mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
fa63a4ac50
commit
bf0deef708
7 changed files with 34 additions and 60 deletions
|
|
@ -25,6 +25,7 @@ import org.meshtastic.core.resources.ic_fingerprint
|
|||
import org.meshtastic.core.resources.ic_fork_left
|
||||
import org.meshtastic.core.resources.ic_home
|
||||
import org.meshtastic.core.resources.ic_icecream
|
||||
import org.meshtastic.core.resources.ic_memory
|
||||
import org.meshtastic.core.resources.ic_military_tech
|
||||
import org.meshtastic.core.resources.ic_mountain_flag
|
||||
import org.meshtastic.core.resources.ic_my_location
|
||||
|
|
@ -75,4 +76,4 @@ val MeshtasticIcons.DeviceNumbers: ImageVector
|
|||
val MeshtasticIcons.Android: ImageVector
|
||||
@Composable get() = vectorResource(Res.drawable.ic_android)
|
||||
val MeshtasticIcons.HardwareModel: ImageVector
|
||||
@Composable get() = vectorResource(Res.drawable.ic_router)
|
||||
@Composable get() = vectorResource(Res.drawable.ic_memory)
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ import org.meshtastic.core.model.MessageStatus
|
|||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.message_delivery_status
|
||||
import org.meshtastic.core.ui.icon.Acknowledged
|
||||
import org.meshtastic.core.ui.icon.AddLink
|
||||
import org.meshtastic.core.ui.icon.CloudUpload
|
||||
import org.meshtastic.core.ui.icon.LinkIcon
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.MessageEnroute
|
||||
import org.meshtastic.core.ui.icon.MessageError
|
||||
import org.meshtastic.core.ui.icon.MqttDelivered
|
||||
import org.meshtastic.core.ui.icon.MqttSyncing
|
||||
import org.meshtastic.core.ui.icon.Warning
|
||||
|
||||
@Composable
|
||||
|
|
@ -36,10 +38,10 @@ fun MessageStatusIcon(status: MessageStatus, modifier: Modifier = Modifier) {
|
|||
val icon =
|
||||
when (status) {
|
||||
MessageStatus.RECEIVED -> MeshtasticIcons.Acknowledged
|
||||
MessageStatus.QUEUED -> MeshtasticIcons.MqttSyncing
|
||||
MessageStatus.QUEUED -> MeshtasticIcons.CloudUpload
|
||||
MessageStatus.DELIVERED -> MeshtasticIcons.MqttDelivered
|
||||
MessageStatus.SFPP_ROUTING -> MeshtasticIcons.MqttSyncing
|
||||
MessageStatus.SFPP_CONFIRMED -> MeshtasticIcons.MqttDelivered
|
||||
MessageStatus.SFPP_ROUTING -> MeshtasticIcons.AddLink
|
||||
MessageStatus.SFPP_CONFIRMED -> MeshtasticIcons.LinkIcon
|
||||
MessageStatus.ENROUTE -> MeshtasticIcons.MessageEnroute
|
||||
MessageStatus.ERROR -> MeshtasticIcons.MessageError
|
||||
else -> MeshtasticIcons.Warning
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import org.jetbrains.compose.resources.stringResource
|
|||
import org.jetbrains.compose.resources.vectorResource
|
||||
import org.meshtastic.core.common.util.formatString
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.DeviceType
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.isUnmessageableRole
|
||||
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
|
||||
|
|
@ -106,6 +107,7 @@ fun NodeItem(
|
|||
onClick: () -> Unit = {},
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
connectionState: ConnectionState,
|
||||
deviceType: DeviceType? = null,
|
||||
isActive: Boolean = false,
|
||||
) {
|
||||
val originalLongName = thatNode.user.long_name.ifEmpty { stringResource(Res.string.unknown_username) }
|
||||
|
|
@ -166,6 +168,7 @@ fun NodeItem(
|
|||
isMuted = isMuted,
|
||||
isUnmessageable = unmessageable,
|
||||
connectionState = connectionState,
|
||||
deviceType = deviceType,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
||||
|
|
@ -400,6 +403,7 @@ private fun NodeItemHeader(
|
|||
isMuted: Boolean,
|
||||
isUnmessageable: Boolean,
|
||||
connectionState: ConnectionState,
|
||||
deviceType: DeviceType?,
|
||||
contentColor: Color,
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -445,6 +449,7 @@ private fun NodeItemHeader(
|
|||
isMuted = isMuted,
|
||||
isUnmessageable = isUnmessageable,
|
||||
connectionState = connectionState,
|
||||
deviceType = deviceType,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import androidx.compose.ui.unit.dp
|
|||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.DeviceType
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.connected
|
||||
import org.meshtastic.core.resources.connecting
|
||||
|
|
@ -46,17 +47,11 @@ import org.meshtastic.core.resources.favorite
|
|||
import org.meshtastic.core.resources.mute_always
|
||||
import org.meshtastic.core.resources.unmessageable
|
||||
import org.meshtastic.core.resources.unmonitored_or_infrastructure
|
||||
import org.meshtastic.core.ui.icon.DeviceSleep
|
||||
import org.meshtastic.core.ui.icon.Disconnected
|
||||
import org.meshtastic.core.ui.component.ConnectionsNavIcon
|
||||
import org.meshtastic.core.ui.icon.Favorite
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.MqttDelivered
|
||||
import org.meshtastic.core.ui.icon.MqttSyncing
|
||||
import org.meshtastic.core.ui.icon.Unmessageable
|
||||
import org.meshtastic.core.ui.icon.VolumeOff
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -68,11 +63,12 @@ fun NodeStatusIcons(
|
|||
isMuted: Boolean,
|
||||
connectionState: ConnectionState,
|
||||
modifier: Modifier = Modifier,
|
||||
deviceType: DeviceType? = null,
|
||||
contentColor: Color = LocalContentColor.current,
|
||||
) {
|
||||
Row(modifier = modifier.padding(4.dp)) {
|
||||
if (isThisNode) {
|
||||
ThisNodeStatusBadge(connectionState)
|
||||
ThisNodeStatusBadge(connectionState = connectionState, deviceType = deviceType)
|
||||
}
|
||||
|
||||
if (isUnmessageable) {
|
||||
|
|
@ -104,7 +100,7 @@ fun NodeStatusIcons(
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ThisNodeStatusBadge(connectionState: ConnectionState) {
|
||||
private fun ThisNodeStatusBadge(connectionState: ConnectionState, deviceType: DeviceType?) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = {
|
||||
|
|
@ -123,55 +119,10 @@ private fun ThisNodeStatusBadge(connectionState: ConnectionState) {
|
|||
},
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
when (connectionState) {
|
||||
ConnectionState.Connected -> ConnectedStatusIcon()
|
||||
ConnectionState.Connecting -> ConnectingStatusIcon()
|
||||
ConnectionState.Disconnected -> DisconnectedStatusIcon()
|
||||
ConnectionState.DeviceSleep -> DeviceSleepStatusIcon()
|
||||
}
|
||||
ConnectionsNavIcon(connectionState = connectionState, deviceType = deviceType, modifier = Modifier.size(24.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConnectedStatusIcon() {
|
||||
Icon(
|
||||
imageVector = MeshtasticIcons.MqttDelivered,
|
||||
contentDescription = stringResource(Res.string.connected),
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.StatusGreen,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConnectingStatusIcon() {
|
||||
Icon(
|
||||
imageVector = MeshtasticIcons.MqttSyncing,
|
||||
contentDescription = stringResource(Res.string.connecting),
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.StatusOrange,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DisconnectedStatusIcon() {
|
||||
Icon(
|
||||
imageVector = MeshtasticIcons.Disconnected,
|
||||
contentDescription = stringResource(Res.string.disconnected),
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.StatusRed,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeviceSleepStatusIcon() {
|
||||
Icon(
|
||||
imageVector = MeshtasticIcons.DeviceSleep,
|
||||
contentDescription = stringResource(Res.string.device_sleeping),
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.StatusYellow,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun StatusBadge(
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ fun NodeListScreen(
|
|||
}
|
||||
|
||||
val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
|
||||
val deviceType by viewModel.deviceType.collectAsStateWithLifecycle()
|
||||
|
||||
val isScrollInProgress by remember {
|
||||
derivedStateOf { listState.isScrollInProgress && (listState.canScrollForward || listState.canScrollBackward) }
|
||||
|
|
@ -187,6 +188,7 @@ fun NodeListScreen(
|
|||
onClick = { navigateToNodeDetails(node.num) },
|
||||
onLongClick = longClick,
|
||||
connectionState = connectionState,
|
||||
deviceType = deviceType,
|
||||
isActive = isActive,
|
||||
)
|
||||
val isThisNode = remember(node) { ourNode?.num == node.num }
|
||||
|
|
|
|||
|
|
@ -23,13 +23,16 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
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.DeviceType
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import org.meshtastic.core.model.RadioController
|
||||
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
|
||||
|
|
@ -45,6 +48,7 @@ class NodeListViewModel(
|
|||
private val radioConfigRepository: RadioConfigRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
private val radioInterfaceService: RadioInterfaceService,
|
||||
val nodeManagementActions: NodeManagementActions,
|
||||
private val getFilteredNodesUseCase: GetFilteredNodesUseCase,
|
||||
val nodeFilterPreferences: NodeFilterPreferences,
|
||||
|
|
@ -58,6 +62,11 @@ class NodeListViewModel(
|
|||
|
||||
val connectionState = serviceRepository.connectionState
|
||||
|
||||
val deviceType: StateFlow<DeviceType?> =
|
||||
radioInterfaceService.currentDeviceAddressFlow
|
||||
.map { address -> address?.let { DeviceType.fromAddress(it) } }
|
||||
.stateInWhileSubscribed(initialValue = null)
|
||||
|
||||
private val nodeSortOption = nodeFilterPreferences.nodeSortOption
|
||||
|
||||
private val _nodeFilterText = savedStateHandle.getStateFlow(KEY_FILTER_TEXT, "")
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import org.meshtastic.core.repository.RadioConfigRepository
|
|||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.testing.FakeNodeRepository
|
||||
import org.meshtastic.core.testing.FakeRadioController
|
||||
import org.meshtastic.core.testing.FakeRadioInterfaceService
|
||||
import org.meshtastic.core.testing.TestDataFactory
|
||||
import org.meshtastic.feature.node.detail.NodeManagementActions
|
||||
import org.meshtastic.feature.node.domain.usecase.GetFilteredNodesUseCase
|
||||
|
|
@ -45,6 +46,7 @@ class NodeListViewModelTest {
|
|||
private lateinit var viewModel: NodeListViewModel
|
||||
private lateinit var nodeRepository: FakeNodeRepository
|
||||
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 nodeFilterPreferences: NodeFilterPreferences = mock(MockMode.autofill)
|
||||
|
|
@ -55,6 +57,7 @@ class NodeListViewModelTest {
|
|||
fun setUp() {
|
||||
nodeRepository = FakeNodeRepository()
|
||||
radioController = FakeRadioController()
|
||||
radioInterfaceService = FakeRadioInterfaceService()
|
||||
|
||||
every { radioConfigRepository.localConfigFlow } returns MutableStateFlow(org.meshtastic.proto.LocalConfig())
|
||||
every { radioConfigRepository.deviceProfileFlow } returns MutableStateFlow(org.meshtastic.proto.DeviceProfile())
|
||||
|
|
@ -79,6 +82,7 @@ class NodeListViewModelTest {
|
|||
radioConfigRepository = radioConfigRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
radioController = radioController,
|
||||
radioInterfaceService = radioInterfaceService,
|
||||
nodeManagementActions = nodeManagementActions,
|
||||
getFilteredNodesUseCase = getFilteredNodesUseCase,
|
||||
nodeFilterPreferences = nodeFilterPreferences,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue