From 27b2c19e699a23a97f677445d542e81fa2167db1 Mon Sep 17 00:00:00 2001 From: James Rich Date: Tue, 5 May 2026 15:15:45 -0500 Subject: [PATCH] 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> --- .../org/meshtastic/app/map/MapViewModel.kt | 4 +- .../org/meshtastic/app/map/MapViewModel.kt | 4 +- .../meshtastic/core/data/di/CoreDataModule.kt | 4 - .../core/data/radio/SdkStateBridgeTest.kt | 189 ++++++++++++++++++ .../meshtastic/core/model/ConnectionAware.kt | 24 +++ .../org/meshtastic/core/model/DeviceAdmin.kt | 2 +- .../meshtastic/core/model/DeviceControl.kt | 2 +- .../meshtastic/core/model/MessageSender.kt | 2 +- .../meshtastic/core/model/RadioController.kt | 12 -- .../core/model/util/MeshDataMapper.kt | 55 ----- .../core/ui/qr/ScannedQrCodeViewModel.kt | 4 +- .../feature/map/BaseMapViewModel.kt | 4 +- .../feature/map/SharedMapViewModel.kt | 4 +- .../node/detail/CommonNodeRequestActions.kt | 22 +- .../node/detail/NodeManagementActions.kt | 10 +- .../feature/node/list/NodeListViewModel.kt | 4 +- .../node/detail/NodeManagementActionsTest.kt | 6 +- .../settings/channel/ChannelViewModel.kt | 4 +- 18 files changed, 252 insertions(+), 104 deletions(-) create mode 100644 core/data/src/commonTest/kotlin/org/meshtastic/core/data/radio/SdkStateBridgeTest.kt create mode 100644 core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionAware.kt delete mode 100644 core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/MeshDataMapper.kt diff --git a/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt b/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt index eefd9df43..90ea43d40 100644 --- a/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt +++ b/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt @@ -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, diff --git a/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt b/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt index 5acd0b1a0..9a05e6d87 100644 --- a/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt +++ b/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt @@ -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, diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/di/CoreDataModule.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/di/CoreDataModule.kt index a2407008b..308a821f0 100644 --- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/di/CoreDataModule.kt +++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/di/CoreDataModule.kt @@ -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 } diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/radio/SdkStateBridgeTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/radio/SdkStateBridgeTest.kt new file mode 100644 index 000000000..bbd361708 --- /dev/null +++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/radio/SdkStateBridgeTest.kt @@ -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 . + */ +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 { + 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(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(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, +) : StorageProvider { + override suspend fun activate(identity: TransportIdentity): DeviceStorage = + InMemoryStorage().also { storage -> + heartbeats.forEach { (nodeId, heartbeatMs) -> + storage.saveHeartbeat(nodeId, heartbeatMs) + } + } +} diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionAware.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionAware.kt new file mode 100644 index 000000000..5e5cb3bc3 --- /dev/null +++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionAware.kt @@ -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 . + */ +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 +} diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceAdmin.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceAdmin.kt index 6554a7da6..c12979e21 100644 --- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceAdmin.kt +++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceAdmin.kt @@ -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) diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceControl.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceControl.kt index 1e42ec820..8cd849205 100644 --- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceControl.kt +++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceControl.kt @@ -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?) diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MessageSender.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MessageSender.kt index 897c2501c..1eaf22ce6 100644 --- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MessageSender.kt +++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MessageSender.kt @@ -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 } diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RadioController.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RadioController.kt index a79c745ba..8913df374 100644 --- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RadioController.kt +++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RadioController.kt @@ -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 - /** * Flow of notifications from the radio client. * diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/MeshDataMapper.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/MeshDataMapper.kt deleted file mode 100644 index 7d35a8e31..000000000 --- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/MeshDataMapper.kt +++ /dev/null @@ -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 . - */ -@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, - ) - } -} diff --git a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt index 5198bd3a1..2df0a6c2b 100644 --- a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt +++ b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt @@ -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()) diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt index 455236e19..153c91a21 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt @@ -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 diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt index bcebdabf6..6d9017f6b 100644 --- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt +++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt @@ -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) diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/CommonNodeRequestActions.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/CommonNodeRequestActions.kt index 1ea463685..19f6843c5 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/CommonNodeRequestActions.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/CommonNodeRequestActions.kt @@ -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)) } diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeManagementActions.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeManagementActions.kt index 17046f3a7..1c48540f1 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeManagementActions.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeManagementActions.kt @@ -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) } } diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListViewModel.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListViewModel.kt index 0d54dfead..2f0ab1c80 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListViewModel.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListViewModel.kt @@ -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, diff --git a/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/detail/NodeManagementActionsTest.kt b/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/detail/NodeManagementActionsTest.kt index 4e65cf290..3233747da 100644 --- a/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/detail/NodeManagementActionsTest.kt +++ b/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/detail/NodeManagementActionsTest.kt @@ -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")) diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/channel/ChannelViewModel.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/channel/ChannelViewModel.kt index 136131241..5c8369cfe 100644 --- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/channel/ChannelViewModel.kt +++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/channel/ChannelViewModel.kt @@ -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() {