mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat/decoupling (#4685)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
40244f8337
commit
2c49db8041
254 changed files with 5132 additions and 2666 deletions
|
|
@ -24,7 +24,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
|
||||
|
||||
|
|
|
|||
|
|
@ -102,9 +102,9 @@ import org.jetbrains.compose.resources.pluralStringResource
|
|||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.HomoglyphCharacterStringTransformer
|
||||
import org.meshtastic.core.database.entity.QuickChatAction
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.util.getChannel
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.alert_bell_text
|
||||
|
|
|
|||
|
|
@ -61,11 +61,10 @@ import kotlinx.coroutines.flow.collectLatest
|
|||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.entity.Reaction
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.Reaction
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.new_messages_below
|
||||
import org.meshtastic.feature.messaging.component.MessageItem
|
||||
|
|
@ -545,7 +544,7 @@ private fun MessageStatusDialog(
|
|||
remember(message.relayNode, nodes, ourNode) {
|
||||
derivedStateOf {
|
||||
message.relayNode?.let { relayNodeId ->
|
||||
Packet.getRelayNode(relayNodeId, nodes, ourNode?.num)?.user?.long_name
|
||||
Node.getRelayNode(relayNodeId, nodes, ourNode?.num)?.user?.long_name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
* Copyright (c) 2025-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
|
||||
|
|
@ -14,10 +14,9 @@
|
|||
* 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.messaging
|
||||
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Node
|
||||
|
||||
/** Defines the various user interactions that can occur on the MessageScreen. */
|
||||
internal sealed interface MessageScreenEvent {
|
||||
|
|
|
|||
|
|
@ -32,21 +32,21 @@ import kotlinx.coroutines.flow.filterNotNull
|
|||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.QuickChatActionRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.entity.ContactSettings
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.domain.usecase.SendMessageUseCase
|
||||
import org.meshtastic.core.model.ContactSettings
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.prefs.emoji.CustomEmojiPrefs
|
||||
import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefs
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.usecase.SendMessageUseCase
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ import androidx.compose.ui.unit.dp
|
|||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.database.entity.Reaction
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.Message
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.Reaction
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.filter_message_label
|
||||
import org.meshtastic.core.resources.message_delivery_status
|
||||
|
|
|
|||
|
|
@ -57,12 +57,11 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.entity.Reaction
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.getStringResFrom
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.Reaction
|
||||
import org.meshtastic.core.model.getStringResFrom
|
||||
import org.meshtastic.core.model.util.getShortDateTime
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.delivery_confirmed
|
||||
|
|
@ -148,7 +147,9 @@ internal fun ReactionRow(
|
|||
|
||||
AnimatedVisibility(emojiGroups.isNotEmpty(), modifier = modifier) {
|
||||
LazyRow(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
items(emojiGroups.entries.toList()) { (emoji, reactions) ->
|
||||
items(emojiGroups.entries.toList()) { entry ->
|
||||
val emoji = entry.key
|
||||
val reactions = entry.value
|
||||
val localReaction = reactions.find { it.user.id == DataPacket.ID_LOCAL || it.user.id == myId }
|
||||
ReactionItem(
|
||||
emoji = emoji,
|
||||
|
|
@ -218,7 +219,7 @@ internal fun ReactionDialog(
|
|||
|
||||
val relayNodeName =
|
||||
reaction.relayNode?.let { relayNodeId ->
|
||||
Packet.getRelayNode(relayNodeId, nodes, ourNode?.num)?.user?.long_name
|
||||
Node.getRelayNode(relayNodeId, nodes, ourNode?.num)?.user?.long_name
|
||||
}
|
||||
|
||||
DeliveryInfo(
|
||||
|
|
@ -236,7 +237,9 @@ internal fun ReactionDialog(
|
|||
}
|
||||
|
||||
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
|
||||
items(groupedEmojis.entries.toList()) { (emoji, reactions) ->
|
||||
items(groupedEmojis.entries.toList()) { entry ->
|
||||
val emoji = entry.key
|
||||
val reactions = entry.value
|
||||
val localReaction = reactions.find { it.user.id == DataPacket.ID_LOCAL || it.user.id == myId }
|
||||
val isSending =
|
||||
localReaction?.status == MessageStatus.QUEUED || localReaction?.status == MessageStatus.ENROUTE
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import dagger.Binds
|
|||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.domain.MessageQueue
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import org.meshtastic.feature.messaging.domain.worker.WorkManagerMessageQueue
|
||||
|
||||
@Module
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ import androidx.work.CoroutineWorker
|
|||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
|
||||
@HiltWorker
|
||||
class SendMessageWorker
|
||||
|
|
@ -47,18 +47,16 @@ constructor(
|
|||
return Result.retry()
|
||||
}
|
||||
|
||||
val packetEntity =
|
||||
val packetData =
|
||||
packetRepository.getPacketByPacketId(packetId)
|
||||
?: return Result.failure() // Packet no longer exists in DB? Do not retry.
|
||||
|
||||
val packetData = packetEntity.packet.data
|
||||
|
||||
return try {
|
||||
radioController.sendMessage(packetData)
|
||||
packetRepository.updateMessageStatus(packetData, MessageStatus.ENROUTE)
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
packetRepository.updateMessageStatus(packetData, MessageStatus.ERROR)
|
||||
packetRepository.updateMessageStatus(packetData, MessageStatus.QUEUED)
|
||||
Result.retry()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import androidx.work.ExistingWorkPolicy
|
|||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.workDataOf
|
||||
import org.meshtastic.core.domain.MessageQueue
|
||||
import org.meshtastic.core.repository.MessageQueue
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ import kotlinx.coroutines.launch
|
|||
import org.jetbrains.compose.resources.pluralStringResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.database.entity.ContactSettings
|
||||
import org.meshtastic.core.model.Contact
|
||||
import org.meshtastic.core.model.ContactSettings
|
||||
import org.meshtastic.core.model.util.TimeConstants
|
||||
import org.meshtastic.core.model.util.formatMuteRemainingTime
|
||||
import org.meshtastic.core.model.util.getChannel
|
||||
|
|
|
|||
|
|
@ -28,16 +28,15 @@ import kotlinx.coroutines.flow.combine
|
|||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.entity.ContactSettings
|
||||
import org.meshtastic.core.database.entity.MyNodeEntity
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.model.Contact
|
||||
import org.meshtastic.core.model.ContactSettings
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.util.getChannel
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import javax.inject.Inject
|
||||
|
|
@ -59,7 +58,7 @@ constructor(
|
|||
val channels = radioConfigRepository.channelSetFlow.stateInWhileSubscribed(initialValue = ChannelSet())
|
||||
|
||||
// Combine node info and myId to reduce argument count in subsequent combines
|
||||
private val identityFlow: Flow<Pair<MyNodeEntity?, String?>> =
|
||||
private val identityFlow: Flow<Pair<MyNodeInfo?, String?>> =
|
||||
combine(nodeRepository.myNodeInfo, nodeRepository.myId) { info, id -> Pair(info, id) }
|
||||
|
||||
/**
|
||||
|
|
@ -78,42 +77,42 @@ constructor(
|
|||
settings,
|
||||
->
|
||||
val (myNodeInfo, myId) = identity
|
||||
val myNodeNum = myNodeInfo?.myNodeNum ?: return@combine emptyList()
|
||||
val myNodeNum = myNodeInfo?.myNodeNum ?: return@combine emptyList<Contact>()
|
||||
// Add empty channel placeholders (always show Broadcast contacts, even when empty)
|
||||
val placeholder =
|
||||
(0 until channelSet.settings.size).associate { ch ->
|
||||
val contactKey = "$ch${DataPacket.ID_BROADCAST}"
|
||||
val data = DataPacket(bytes = null, dataType = 1, time = 0L, channel = ch)
|
||||
contactKey to Packet(0L, myNodeNum, 1, contactKey, 0L, true, data)
|
||||
contactKey to data
|
||||
}
|
||||
|
||||
(contacts + (placeholder - contacts.keys)).values.collectionsMap { packet ->
|
||||
val data = packet.data
|
||||
val contactKey = packet.contact_key
|
||||
|
||||
(contacts + (placeholder - contacts.keys)).entries.collectionsMap { entry ->
|
||||
val contactKey = entry.key
|
||||
val packetData = entry.value
|
||||
// Determine if this is my message (originated on this device)
|
||||
val fromLocal = (data.from == DataPacket.ID_LOCAL || (myId != null && data.from == myId))
|
||||
val toBroadcast = data.to == DataPacket.ID_BROADCAST
|
||||
val fromLocal =
|
||||
(packetData.from == DataPacket.ID_LOCAL || (myId != null && packetData.from == myId))
|
||||
val toBroadcast = packetData.to == DataPacket.ID_BROADCAST
|
||||
|
||||
// grab usernames from NodeInfo
|
||||
val userId = if (fromLocal) data.to else data.from
|
||||
val userId = if (fromLocal) packetData.to else packetData.from
|
||||
val user = nodeRepository.getUser(userId ?: DataPacket.ID_BROADCAST)
|
||||
val node = nodeRepository.getNode(userId ?: DataPacket.ID_BROADCAST)
|
||||
|
||||
val shortName = user.short_name
|
||||
val longName =
|
||||
if (toBroadcast) {
|
||||
channelSet.getChannel(data.channel)?.name ?: "Channel ${data.channel}"
|
||||
channelSet.getChannel(packetData.channel)?.name ?: "Channel ${packetData.channel}"
|
||||
} else {
|
||||
user.long_name
|
||||
}
|
||||
|
||||
Contact(
|
||||
contactKey = contactKey,
|
||||
shortName = if (toBroadcast) data.channel.toString() else shortName,
|
||||
shortName = if (toBroadcast) packetData.channel.toString() else shortName,
|
||||
longName = longName,
|
||||
lastMessageTime = if (data.time != 0L) data.time else null,
|
||||
lastMessageText = if (fromLocal) data.text else "$shortName: ${data.text}",
|
||||
lastMessageTime = if (packetData.time != 0L) packetData.time else null,
|
||||
lastMessageText = if (fromLocal) packetData.text else "$shortName: ${packetData.text}",
|
||||
unreadCount = packetRepository.getUnreadCount(contactKey),
|
||||
messageCount = packetRepository.getMessageCount(contactKey),
|
||||
isMuted = settings[contactKey]?.isMuted == true,
|
||||
|
|
@ -140,36 +139,41 @@ constructor(
|
|||
val myId = params.myId
|
||||
|
||||
packetRepository.getContactsPaged().map { pagingData ->
|
||||
pagingData.map { packet ->
|
||||
val data = packet.data
|
||||
val contactKey = packet.contact_key
|
||||
pagingData.map { packetData: DataPacket ->
|
||||
val contactKey =
|
||||
"${packetData.channel}${packetData.to}" // This might be wrong, need to check how contactKey
|
||||
// is derived in PagingSource
|
||||
|
||||
// Determine if this is my message (originated on this device)
|
||||
val fromLocal = (data.from == DataPacket.ID_LOCAL || (myId != null && data.from == myId))
|
||||
val toBroadcast = data.to == DataPacket.ID_BROADCAST
|
||||
val fromLocal =
|
||||
(packetData.from == DataPacket.ID_LOCAL || (myId != null && packetData.from == myId))
|
||||
val toBroadcast = packetData.to == DataPacket.ID_BROADCAST
|
||||
|
||||
// grab usernames from NodeInfo
|
||||
val userId = if (fromLocal) data.to else data.from
|
||||
val userId = if (fromLocal) packetData.to else packetData.from
|
||||
val user = nodeRepository.getUser(userId ?: DataPacket.ID_BROADCAST)
|
||||
val node = nodeRepository.getNode(userId ?: DataPacket.ID_BROADCAST)
|
||||
|
||||
val shortName = user.short_name
|
||||
val longName =
|
||||
if (toBroadcast) {
|
||||
channelSet.getChannel(data.channel)?.name ?: "Channel ${data.channel}"
|
||||
channelSet.getChannel(packetData.channel)?.name ?: "Channel ${packetData.channel}"
|
||||
} else {
|
||||
user.long_name
|
||||
}
|
||||
|
||||
val contactKeyComputed =
|
||||
if (toBroadcast) "${packetData.channel}${DataPacket.ID_BROADCAST}" else contactKey
|
||||
|
||||
Contact(
|
||||
contactKey = contactKey,
|
||||
shortName = if (toBroadcast) data.channel.toString() else shortName,
|
||||
contactKey = contactKeyComputed,
|
||||
shortName = if (toBroadcast) packetData.channel.toString() else shortName,
|
||||
longName = longName,
|
||||
lastMessageTime = if (data.time != 0L) data.time else null,
|
||||
lastMessageText = if (fromLocal) data.text else "$shortName: ${data.text}",
|
||||
unreadCount = packetRepository.getUnreadCount(contactKey),
|
||||
messageCount = packetRepository.getMessageCount(contactKey),
|
||||
isMuted = settings[contactKey]?.isMuted == true,
|
||||
lastMessageTime = if (packetData.time != 0L) packetData.time else null,
|
||||
lastMessageText = if (fromLocal) packetData.text else "$shortName: ${packetData.text}",
|
||||
unreadCount = packetRepository.getUnreadCount(contactKeyComputed),
|
||||
messageCount = packetRepository.getMessageCount(contactKeyComputed),
|
||||
isMuted = settings[contactKeyComputed]?.isMuted == true,
|
||||
isUnmessageable = user.is_unmessagable ?: false,
|
||||
nodeColors =
|
||||
if (!toBroadcast) {
|
||||
|
|
|
|||
|
|
@ -30,17 +30,16 @@ import io.mockk.just
|
|||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.database.entity.Packet
|
||||
import org.meshtastic.core.database.entity.PacketEntity
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
|
|
@ -62,11 +61,8 @@ class SendMessageWorkerTest {
|
|||
fun `doWork returns success when packet is sent successfully`() = runTest {
|
||||
// Arrange
|
||||
val packetId = 12345
|
||||
val dataPacket = DataPacket("dest", 0, "Hello")
|
||||
val packet = mockk<Packet>(relaxed = true)
|
||||
val packetEntity = PacketEntity(packet = packet)
|
||||
every { packet.data } returns dataPacket
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns packetEntity
|
||||
val dataPacket = DataPacket(to = "dest", bytes = "Hello".encodeToByteArray().toByteString(), dataType = 0)
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Connected)
|
||||
coEvery { radioController.sendMessage(any()) } just Runs
|
||||
coEvery { packetRepository.updateMessageStatus(any(), any()) } just Runs
|
||||
|
|
@ -99,11 +95,8 @@ class SendMessageWorkerTest {
|
|||
fun `doWork returns retry when radio is disconnected`() = runTest {
|
||||
// Arrange
|
||||
val packetId = 12345
|
||||
val dataPacket = DataPacket("dest", 0, "Hello")
|
||||
val packet = mockk<Packet>(relaxed = true)
|
||||
val packetEntity = PacketEntity(packet = packet)
|
||||
every { packet.data } returns dataPacket
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns packetEntity
|
||||
val dataPacket = DataPacket(to = "dest", bytes = "Hello".encodeToByteArray().toByteString(), dataType = 0)
|
||||
coEvery { packetRepository.getPacketByPacketId(packetId) } returns dataPacket
|
||||
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
|
||||
|
||||
val worker =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue