diff --git a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/icon/Device.kt b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/icon/Device.kt index 66060116f..6bf669ab6 100644 --- a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/icon/Device.kt +++ b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/icon/Device.kt @@ -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) diff --git a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageStatusIcon.kt b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageStatusIcon.kt index 501a3f7dc..7b361d497 100644 --- a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageStatusIcon.kt +++ b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageStatusIcon.kt @@ -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 diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeItem.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeItem.kt index 514be15e7..ad6714db7 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeItem.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeItem.kt @@ -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, ) } diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt index 007c12c96..1bbafad6a 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeStatusIcons.kt @@ -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( diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt index 9c2c208f4..5a156b836 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt @@ -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 } 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 df65a3477..172a296eb 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 @@ -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 = + 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, "") diff --git a/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/list/NodeListViewModelTest.kt b/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/list/NodeListViewModelTest.kt index 602134aa0..9511a2da1 100644 --- a/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/list/NodeListViewModelTest.kt +++ b/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/list/NodeListViewModelTest.kt @@ -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,