mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-12 00:28:20 -04:00
refactor: narrow ViewModel injections, add ConnectionAware, delete dead code, integration tests
ViewModel Narrowing: - V1: Created ConnectionAware interface; MessageSender, DeviceAdmin, DeviceControl extend it - V2: Narrowed 6 ViewModels/actions to focused sub-interfaces (DeviceAdmin, MessageSender, DataRequester, DeviceControl) Cleanup: - C1: Deleted dead MeshDataMapper and its DI registration Integration Tests: - T2: SdkStateBridgeTest verifying WentOffline/CameOnline presence handling - Verified Koin resolution, full test suite passes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.MessageSender
|
||||
import org.meshtastic.core.repository.MapPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
@@ -37,7 +37,7 @@ class MapViewModel(
|
||||
mapPrefs: MapPrefs,
|
||||
packetRepository: PacketRepository,
|
||||
nodeRepository: NodeRepository,
|
||||
radioController: RadioController,
|
||||
radioController: MessageSender,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
buildConfigProvider: BuildConfigProvider,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
|
||||
@@ -50,7 +50,7 @@ import org.meshtastic.app.map.model.CustomTileProviderConfig
|
||||
import org.meshtastic.app.map.prefs.map.GoogleMapsPrefs
|
||||
import org.meshtastic.app.map.repository.CustomTileProviderRepository
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.MessageSender
|
||||
import org.meshtastic.core.repository.MapPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
@@ -89,7 +89,7 @@ class MapViewModel(
|
||||
nodeRepository: NodeRepository,
|
||||
packetRepository: PacketRepository,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
radioController: RadioController,
|
||||
radioController: MessageSender,
|
||||
private val customTileProviderRepository: CustomTileProviderRepository,
|
||||
uiPrefs: UiPrefs,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
|
||||
@@ -19,14 +19,10 @@ package org.meshtastic.core.data.di
|
||||
import org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Module
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.model.util.MeshDataMapper
|
||||
import org.meshtastic.core.model.util.NodeIdLookup
|
||||
import kotlin.time.Clock
|
||||
|
||||
@Module
|
||||
@ComponentScan("org.meshtastic.core.data")
|
||||
class CoreDataModule {
|
||||
@Single fun provideMeshDataMapper(nodeIdLookup: NodeIdLookup): MeshDataMapper = MeshDataMapper(nodeIdLookup)
|
||||
|
||||
@Single fun provideClock(): Clock = Clock.System
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.core.data.radio
|
||||
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.mock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.repository.MeshLocationManager
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.testing.FakeNodeRepository
|
||||
import org.meshtastic.core.testing.FakeRadioController
|
||||
import org.meshtastic.core.testing.FakeServiceRepository
|
||||
import org.meshtastic.core.testing.FakeUiPrefs
|
||||
import org.meshtastic.proto.Data
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
import org.meshtastic.proto.PortNum
|
||||
import org.meshtastic.proto.Position
|
||||
import org.meshtastic.proto.User
|
||||
import org.meshtastic.sdk.DeviceStorage
|
||||
import org.meshtastic.sdk.NodeId
|
||||
import org.meshtastic.sdk.RadioClient
|
||||
import org.meshtastic.sdk.StorageProvider
|
||||
import org.meshtastic.sdk.TransportIdentity
|
||||
import org.meshtastic.sdk.testing.FakeRadioTransport
|
||||
import org.meshtastic.sdk.testing.InMemoryStorage
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class SdkStateBridgeTest {
|
||||
|
||||
@Test
|
||||
fun `went offline marks node offline in repository`() = runTest {
|
||||
val remoteNode = NodeId(0x22222222)
|
||||
val staleHeartbeatMs = Clock.System.now().toEpochMilliseconds() - 5.seconds.inWholeMilliseconds
|
||||
val nodeRepository =
|
||||
FakeNodeRepository().apply {
|
||||
setNodes(
|
||||
listOf(
|
||||
Node(
|
||||
num = remoteNode.raw,
|
||||
user = User(id = "!22222222", long_name = "Test Node"),
|
||||
lastHeard = (Clock.System.now().toEpochMilliseconds() / 1000).toInt(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
val (_, client) = connectedClient(SeededHeartbeatStorageProvider(mapOf(remoteNode to staleHeartbeatMs)))
|
||||
buildBridge(client, nodeRepository)
|
||||
|
||||
client.connect()
|
||||
runCurrent()
|
||||
advanceTimeBy(30.seconds)
|
||||
runCurrent()
|
||||
|
||||
val updated = nodeRepository.nodeDBbyNum.value.getValue(remoteNode.raw)
|
||||
assertTrue(updated.lastHeard <= (staleHeartbeatMs / 1000).toInt())
|
||||
assertFalse(updated.isOnline)
|
||||
|
||||
client.disconnect()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `came online marks node online in repository`() = runTest {
|
||||
val remoteNode = NodeId(0x33333333)
|
||||
val staleHeartbeatMs = Clock.System.now().toEpochMilliseconds() - 5.seconds.inWholeMilliseconds
|
||||
val nodeRepository =
|
||||
FakeNodeRepository().apply {
|
||||
setNodes(
|
||||
listOf(
|
||||
Node(
|
||||
num = remoteNode.raw,
|
||||
user = User(id = "!33333333", long_name = "Test Node"),
|
||||
lastHeard = (staleHeartbeatMs / 1000).toInt(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
val (transport, client) = connectedClient(SeededHeartbeatStorageProvider(mapOf(remoteNode to staleHeartbeatMs)))
|
||||
buildBridge(client, nodeRepository)
|
||||
|
||||
client.connect()
|
||||
runCurrent()
|
||||
advanceTimeBy(30.seconds)
|
||||
runCurrent()
|
||||
|
||||
transport.injectPacket(
|
||||
MeshPacket(
|
||||
from = remoteNode.raw,
|
||||
to = 0,
|
||||
decoded = Data(portnum = PortNum.TEXT_MESSAGE_APP),
|
||||
),
|
||||
)
|
||||
runCurrent()
|
||||
|
||||
val updated = nodeRepository.nodeDBbyNum.value.getValue(remoteNode.raw)
|
||||
assertTrue(updated.lastHeard > (staleHeartbeatMs / 1000).toInt())
|
||||
assertTrue(updated.isOnline)
|
||||
|
||||
client.disconnect()
|
||||
}
|
||||
|
||||
private fun TestScope.connectedClient(
|
||||
storage: StorageProvider,
|
||||
myNodeNum: Int = 0x11111111,
|
||||
presenceTimeout: Duration = 1.seconds,
|
||||
): Pair<FakeRadioTransport, RadioClient> {
|
||||
val transport = FakeRadioTransport(identity = TransportIdentity("fake:state-bridge"), autoHandshake = true, nodeNum = myNodeNum)
|
||||
val client =
|
||||
RadioClient.Builder()
|
||||
.transport(transport)
|
||||
.storage(storage)
|
||||
.coroutineContext(backgroundScope.coroutineContext)
|
||||
.autoSyncTimeOnConnect(false)
|
||||
.presenceTimeout(presenceTimeout)
|
||||
.build()
|
||||
return transport to client
|
||||
}
|
||||
|
||||
private fun TestScope.buildBridge(
|
||||
client: RadioClient,
|
||||
nodeRepository: FakeNodeRepository,
|
||||
): SdkStateBridge =
|
||||
SdkStateBridge(
|
||||
accessor = TestRadioClientAccessor(client),
|
||||
serviceRepository = FakeServiceRepository(),
|
||||
nodeRepository = nodeRepository,
|
||||
packetRepository = lazyOf(mock<PacketRepository>(MockMode.autofill)),
|
||||
locationManager = NoOpLocationManager,
|
||||
uiPrefs = FakeUiPrefs(),
|
||||
radioController = FakeRadioController(),
|
||||
dispatchers = CoroutineDispatchers(
|
||||
io = backgroundScope.coroutineContext[kotlin.coroutines.ContinuationInterceptor] as kotlinx.coroutines.CoroutineDispatcher,
|
||||
main = backgroundScope.coroutineContext[kotlin.coroutines.ContinuationInterceptor] as kotlinx.coroutines.CoroutineDispatcher,
|
||||
default = backgroundScope.coroutineContext[kotlin.coroutines.ContinuationInterceptor] as kotlinx.coroutines.CoroutineDispatcher,
|
||||
),
|
||||
)
|
||||
|
||||
private class TestRadioClientAccessor(client: RadioClient) : RadioClientAccessor {
|
||||
override val client = MutableStateFlow<RadioClient?>(client)
|
||||
|
||||
override fun rebuildAndConnectAsync() = Unit
|
||||
|
||||
override fun disconnect() = Unit
|
||||
}
|
||||
|
||||
private object NoOpLocationManager : MeshLocationManager {
|
||||
override fun start(scope: CoroutineScope, sendPositionFn: (Position) -> Unit) = Unit
|
||||
|
||||
override fun stop() = Unit
|
||||
}
|
||||
}
|
||||
|
||||
private class SeededHeartbeatStorageProvider(
|
||||
private val heartbeats: Map<NodeId, Long>,
|
||||
) : StorageProvider {
|
||||
override suspend fun activate(identity: TransportIdentity): DeviceStorage =
|
||||
InMemoryStorage().also { storage ->
|
||||
heartbeats.forEach { (nodeId, heartbeatMs) ->
|
||||
storage.saveHeartbeat(nodeId, heartbeatMs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.core.model
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/** Provides read-only access to the app's connection state. */
|
||||
interface ConnectionAware {
|
||||
val connectionState: StateFlow<ConnectionState>
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import org.meshtastic.proto.Channel
|
||||
import org.meshtastic.proto.Config
|
||||
|
||||
/** Focused interface for local device configuration and edit sessions. */
|
||||
interface DeviceAdmin {
|
||||
interface DeviceAdmin : ConnectionAware {
|
||||
suspend fun setLocalConfig(config: Config)
|
||||
suspend fun setLocalChannel(channel: Channel)
|
||||
suspend fun beginEditSettings(destNum: Int)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.meshtastic.core.model
|
||||
|
||||
/** Focused interface for device lifecycle control. */
|
||||
interface DeviceControl {
|
||||
interface DeviceControl : ConnectionAware {
|
||||
suspend fun reboot(destNum: Int, packetId: Int)
|
||||
suspend fun rebootToDfu(nodeNum: Int)
|
||||
suspend fun requestRebootOta(requestId: Int, destNum: Int, mode: Int, hash: ByteArray?)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.meshtastic.core.model
|
||||
|
||||
/** Focused interface for sending messages over the mesh. */
|
||||
interface MessageSender {
|
||||
interface MessageSender : ConnectionAware {
|
||||
suspend fun sendMessage(packet: DataPacket)
|
||||
fun getPacketId(): Int
|
||||
}
|
||||
|
||||
@@ -26,18 +26,6 @@ import org.meshtastic.proto.ClientNotification
|
||||
* This super-interface remains for backward compatibility with existing injections.
|
||||
*/
|
||||
interface RadioController : MessageSender, DeviceAdmin, RemoteAdmin, DeviceControl, DataRequester {
|
||||
/**
|
||||
* Canonical app-level connection state, delegated from [ServiceRepository][connectionState].
|
||||
*
|
||||
* This exposes the same single source of truth as `ServiceRepository.connectionState`, surfaced through the
|
||||
* controller interface for convenience in feature modules and ViewModels that depend on [RadioController] rather
|
||||
* than [ServiceRepository] directly.
|
||||
*
|
||||
* This is **not** the transport-level state — it reflects the fully reconciled app-level state including handshake
|
||||
* progress and device sleep policy.
|
||||
*/
|
||||
val connectionState: StateFlow<ConnectionState>
|
||||
|
||||
/**
|
||||
* Flow of notifications from the radio client.
|
||||
*
|
||||
|
||||
@@ -1,55 +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/>.
|
||||
*/
|
||||
@file:Suppress("MagicNumber")
|
||||
|
||||
package org.meshtastic.core.model.util
|
||||
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
|
||||
/**
|
||||
* Utility class to map [MeshPacket] protobufs to [DataPacket] domain models.
|
||||
*
|
||||
* This class is platform-agnostic and can be used in shared logic.
|
||||
*/
|
||||
open class MeshDataMapper(private val nodeIdLookup: NodeIdLookup) {
|
||||
|
||||
/** Maps a [MeshPacket] to a [DataPacket], or returns null if the packet has no decoded data. */
|
||||
open fun toDataPacket(packet: MeshPacket): DataPacket? {
|
||||
val decoded = packet.decoded ?: return null
|
||||
return DataPacket(
|
||||
from = packet.from,
|
||||
to = packet.to,
|
||||
time = packet.rx_time * 1000L,
|
||||
id = packet.id,
|
||||
dataType = decoded.portnum.value,
|
||||
bytes = decoded.payload.toByteArray().toByteString(),
|
||||
hopLimit = packet.hop_limit,
|
||||
channel = if (packet.pki_encrypted == true) DataPacket.PKC_CHANNEL_INDEX else packet.channel,
|
||||
wantAck = packet.want_ack == true,
|
||||
hopStart = packet.hop_start,
|
||||
snr = packet.rx_snr,
|
||||
rssi = packet.rx_rssi,
|
||||
replyId = decoded.reply_id,
|
||||
relayNode = packet.relay_node,
|
||||
viaMqtt = packet.via_mqtt == true,
|
||||
emoji = decoded.emoji,
|
||||
transportMechanism = packet.transport_mechanism.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ package org.meshtastic.core.ui.qr
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.DeviceAdmin
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.ui.util.getChannelList
|
||||
import org.meshtastic.core.ui.viewmodel.safeLaunch
|
||||
@@ -31,7 +31,7 @@ import org.meshtastic.proto.LocalConfig
|
||||
@KoinViewModel
|
||||
class ScannedQrCodeViewModel(
|
||||
private val radioConfigRepository: RadioConfigRepository,
|
||||
private val radioController: RadioController,
|
||||
private val radioController: DeviceAdmin,
|
||||
) : ViewModel() {
|
||||
|
||||
val channels = radioConfigRepository.channelSetFlow.stateInWhileSubscribed(initialValue = ChannelSet())
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.meshtastic.core.common.util.ioDispatcher
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.MessageSender
|
||||
import org.meshtastic.core.model.TracerouteOverlay
|
||||
import org.meshtastic.core.repository.MapPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
@@ -55,7 +55,7 @@ open class BaseMapViewModel(
|
||||
protected val mapPrefs: MapPrefs,
|
||||
protected val nodeRepository: NodeRepository,
|
||||
private val packetRepository: PacketRepository,
|
||||
private val radioController: RadioController,
|
||||
private val radioController: MessageSender,
|
||||
) : ViewModel() {
|
||||
|
||||
val myNodeInfo = nodeRepository.myNodeInfo
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.meshtastic.feature.map
|
||||
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.MessageSender
|
||||
import org.meshtastic.core.repository.MapPrefs
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
@@ -27,5 +27,5 @@ class SharedMapViewModel(
|
||||
mapPrefs: MapPrefs,
|
||||
nodeRepository: NodeRepository,
|
||||
packetRepository: PacketRepository,
|
||||
radioController: RadioController,
|
||||
radioController: MessageSender,
|
||||
) : BaseMapViewModel(mapPrefs, nodeRepository, packetRepository, radioController)
|
||||
|
||||
@@ -26,8 +26,9 @@ 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.DataRequester
|
||||
import org.meshtastic.core.model.MessageSender
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.TelemetryType
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.UiText
|
||||
@@ -48,7 +49,8 @@ import org.meshtastic.core.ui.util.SnackbarManager
|
||||
@Single(binds = [NodeRequestActions::class])
|
||||
class CommonNodeRequestActions
|
||||
constructor(
|
||||
private val radioController: RadioController,
|
||||
private val dataRequester: DataRequester,
|
||||
private val messageSender: MessageSender,
|
||||
private val snackbarManager: SnackbarManager,
|
||||
) : NodeRequestActions {
|
||||
|
||||
@@ -65,7 +67,7 @@ constructor(
|
||||
override fun requestUserInfo(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
scope.launch(ioDispatcher) {
|
||||
Logger.i { "Requesting UserInfo for '$destNum'" }
|
||||
radioController.requestUserInfo(destNum)
|
||||
dataRequester.requestUserInfo(destNum)
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.user_info, longName))
|
||||
}
|
||||
}
|
||||
@@ -73,8 +75,8 @@ constructor(
|
||||
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)
|
||||
val packetId = messageSender.getPacketId()
|
||||
dataRequester.requestNeighborInfo(packetId, destNum)
|
||||
_lastRequestNeighborTimes.update { it + (destNum to nowMillis) }
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.neighbor_info, longName))
|
||||
}
|
||||
@@ -83,7 +85,7 @@ constructor(
|
||||
override fun requestPosition(scope: CoroutineScope, destNum: Int, longName: String, position: Position) {
|
||||
scope.launch(ioDispatcher) {
|
||||
Logger.i { "Requesting position for '$destNum'" }
|
||||
radioController.requestPosition(destNum, position)
|
||||
dataRequester.requestPosition(destNum, position)
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.position, longName))
|
||||
}
|
||||
}
|
||||
@@ -91,8 +93,8 @@ constructor(
|
||||
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)
|
||||
val packetId = messageSender.getPacketId()
|
||||
dataRequester.requestTelemetry(packetId, destNum, type.ordinal)
|
||||
|
||||
val typeRes =
|
||||
when (type) {
|
||||
@@ -112,8 +114,8 @@ constructor(
|
||||
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)
|
||||
val packetId = messageSender.getPacketId()
|
||||
dataRequester.requestTraceroute(packetId, destNum)
|
||||
_lastTracerouteTime.value = nowMillis
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName))
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ 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.DeviceControl
|
||||
import org.meshtastic.core.model.MessageSender
|
||||
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
|
||||
@@ -47,7 +48,8 @@ open class NodeManagementActions
|
||||
constructor(
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
private val deviceControl: DeviceControl,
|
||||
private val messageSender: MessageSender,
|
||||
private val alertManager: AlertManager,
|
||||
) {
|
||||
open fun requestRemoveNode(scope: CoroutineScope, node: Node, onAfterRemove: () -> Unit = {}) {
|
||||
@@ -64,8 +66,8 @@ constructor(
|
||||
open fun removeNode(scope: CoroutineScope, nodeNum: Int) {
|
||||
scope.launch(ioDispatcher) {
|
||||
Logger.i { "Removing node '$nodeNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.removeByNodenum(packetId, nodeNum)
|
||||
val packetId = messageSender.getPacketId()
|
||||
deviceControl.removeByNodenum(packetId, nodeNum)
|
||||
nodeRepository.deleteNode(nodeNum)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.model.DeviceType
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.DeviceAdmin
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.RadioPrefs
|
||||
@@ -47,7 +47,7 @@ class NodeListViewModel(
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val radioConfigRepository: RadioConfigRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
private val radioController: DeviceAdmin,
|
||||
private val radioPrefs: RadioPrefs,
|
||||
val nodeManagementActions: NodeManagementActions,
|
||||
private val getFilteredNodesUseCase: GetFilteredNodesUseCase,
|
||||
|
||||
@@ -46,7 +46,8 @@ class NodeManagementActionsTest {
|
||||
NodeManagementActions(
|
||||
nodeRepository = nodeRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
radioController = radioController,
|
||||
deviceControl = radioController,
|
||||
messageSender = radioController,
|
||||
alertManager = alertManager,
|
||||
)
|
||||
|
||||
@@ -78,7 +79,8 @@ class NodeManagementActionsTest {
|
||||
NodeManagementActions(
|
||||
nodeRepository = nodeRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
radioController = radioController,
|
||||
deviceControl = radioController,
|
||||
messageSender = radioController,
|
||||
alertManager = realAlertManager,
|
||||
)
|
||||
val node = Node(num = 123, user = User(long_name = "Test Node"))
|
||||
|
||||
@@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.common.util.CommonUri
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.DeviceAdmin
|
||||
import org.meshtastic.core.model.util.toChannelSet
|
||||
import org.meshtastic.core.repository.DataPair
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
@@ -37,7 +37,7 @@ import org.meshtastic.proto.LocalConfig
|
||||
|
||||
@KoinViewModel
|
||||
class ChannelViewModel(
|
||||
private val radioController: RadioController,
|
||||
private val radioController: DeviceAdmin,
|
||||
private val radioConfigRepository: RadioConfigRepository,
|
||||
private val analytics: PlatformAnalytics,
|
||||
) : ViewModel() {
|
||||
|
||||
Reference in New Issue
Block a user