mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-12 08:42:01 -04:00
feat: SFPP delegation to SDK, NeighborInfo SDK model, congestion + S&F badges
- SdkStateBridge: handle SfppLinkProvided/SfppCanonAnnounced events from SDK - StoreForwardPacketHandlerImpl: SFPP parsing removed (SDK-owned) - NeighborInfoHandlerImpl: delegate formatting to SDK NeighborInfo.fromProto() - NodeStatusIcons: CongestionBadge (yellow/orange/red for MEDIUM/HIGH/CRITICAL) - NodeStatusIcons: StoreForwardBadge (blue cloud icon for S&F servers) - NodeListViewModel: expose congestionLevel + storeForwardServers flows - Tests updated for SFPP bridge coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -90,6 +90,7 @@ import org.meshtastic.core.ui.icon.ChannelUtilization
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.Notes
|
||||
import org.meshtastic.proto.Config
|
||||
import org.meshtastic.sdk.CongestionLevel
|
||||
|
||||
private const val ACTIVE_ALPHA = 0.5f
|
||||
private const val INACTIVE_ALPHA = 0.2f
|
||||
@@ -102,12 +103,14 @@ fun NodeItem(
|
||||
thatNode: Node,
|
||||
distanceUnits: Int,
|
||||
tempInFahrenheit: Boolean,
|
||||
congestionLevel: CongestionLevel? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
connectionState: ConnectionState,
|
||||
deviceType: DeviceType? = null,
|
||||
isActive: Boolean = false,
|
||||
isStoreForwardServer: Boolean = false,
|
||||
) {
|
||||
val originalLongName = thatNode.user.long_name.ifEmpty { stringResource(Res.string.unknown_username) }
|
||||
val isMuted = remember(thatNode) { thatNode.isMuted }
|
||||
@@ -167,7 +170,9 @@ fun NodeItem(
|
||||
isMuted = isMuted,
|
||||
isUnmessageable = unmessageable,
|
||||
connectionState = connectionState,
|
||||
congestionLevel = congestionLevel,
|
||||
deviceType = deviceType,
|
||||
isStoreForwardServer = isStoreForwardServer,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
|
||||
@@ -395,7 +400,9 @@ private fun NodeItemHeader(
|
||||
isMuted: Boolean,
|
||||
isUnmessageable: Boolean,
|
||||
connectionState: ConnectionState,
|
||||
congestionLevel: CongestionLevel?,
|
||||
deviceType: DeviceType?,
|
||||
isStoreForwardServer: Boolean,
|
||||
contentColor: Color,
|
||||
) {
|
||||
Row(
|
||||
@@ -441,7 +448,9 @@ private fun NodeItemHeader(
|
||||
isMuted = isMuted,
|
||||
isUnmessageable = isUnmessageable,
|
||||
connectionState = connectionState,
|
||||
congestionLevel = congestionLevel,
|
||||
deviceType = deviceType,
|
||||
isStoreForwardServer = isStoreForwardServer,
|
||||
contentColor = contentColor,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,11 +48,17 @@ 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.component.ConnectionsNavIcon
|
||||
import org.meshtastic.core.ui.icon.CloudDownload
|
||||
import org.meshtastic.core.ui.icon.Favorite
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.Unmessageable
|
||||
import org.meshtastic.core.ui.icon.VolumeOff
|
||||
import org.meshtastic.core.ui.icon.Warning
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusBlue
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
|
||||
import org.meshtastic.sdk.CongestionLevel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -62,14 +68,19 @@ fun NodeStatusIcons(
|
||||
isFavorite: Boolean,
|
||||
isMuted: Boolean,
|
||||
connectionState: ConnectionState,
|
||||
congestionLevel: CongestionLevel? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
deviceType: DeviceType? = null,
|
||||
isStoreForwardServer: Boolean = false,
|
||||
contentColor: Color = LocalContentColor.current,
|
||||
) {
|
||||
Row(modifier = modifier.padding(4.dp)) {
|
||||
if (isThisNode) {
|
||||
ThisNodeStatusBadge(connectionState = connectionState, deviceType = deviceType)
|
||||
}
|
||||
if (isThisNode && congestionLevel != null && congestionLevel != CongestionLevel.LOW) {
|
||||
CongestionBadge(congestionLevel)
|
||||
}
|
||||
|
||||
if (isUnmessageable) {
|
||||
StatusBadge(
|
||||
@@ -79,6 +90,9 @@ fun NodeStatusIcons(
|
||||
tint = contentColor,
|
||||
)
|
||||
}
|
||||
if (isStoreForwardServer) {
|
||||
StoreForwardBadge()
|
||||
}
|
||||
if (isMuted && !isThisNode) {
|
||||
StatusBadge(
|
||||
imageVector = MeshtasticIcons.VolumeOff,
|
||||
@@ -125,6 +139,47 @@ private fun ThisNodeStatusBadge(connectionState: ConnectionState, deviceType: De
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun StoreForwardBadge() {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = { PlainTooltip { Text("Store & Forward server") } },
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MeshtasticIcons.CloudDownload,
|
||||
contentDescription = "Store & Forward server",
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.StatusBlue,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CongestionBadge(level: CongestionLevel) {
|
||||
val color =
|
||||
when (level) {
|
||||
CongestionLevel.MEDIUM -> MaterialTheme.colorScheme.StatusYellow
|
||||
CongestionLevel.HIGH -> MaterialTheme.colorScheme.StatusOrange
|
||||
CongestionLevel.CRITICAL -> MaterialTheme.colorScheme.StatusRed
|
||||
else -> return
|
||||
}
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = { PlainTooltip { Text("Channel: ${level.name}") } },
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MeshtasticIcons.Warning,
|
||||
contentDescription = "Congestion: ${level.name}",
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun StatusBadge(
|
||||
|
||||
@@ -98,6 +98,8 @@ fun NodeListScreen(
|
||||
|
||||
val nodes by viewModel.nodeList.collectAsStateWithLifecycle()
|
||||
val ourNode by viewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
val congestionLevel by viewModel.congestionLevel.collectAsStateWithLifecycle()
|
||||
val storeForwardServers by viewModel.storeForwardServers.collectAsStateWithLifecycle()
|
||||
val onlineNodeCount by viewModel.onlineNodeCount.collectAsStateWithLifecycle(0)
|
||||
val totalNodeCount by viewModel.totalNodeCount.collectAsStateWithLifecycle(0)
|
||||
val unfilteredNodes by viewModel.unfilteredNodeList.collectAsStateWithLifecycle()
|
||||
@@ -203,11 +205,13 @@ fun NodeListScreen(
|
||||
thatNode = node,
|
||||
distanceUnits = state.distanceUnits,
|
||||
tempInFahrenheit = state.tempInFahrenheit,
|
||||
congestionLevel = congestionLevel,
|
||||
onClick = { navigateToNodeDetails(node.num) },
|
||||
onLongClick = longClick,
|
||||
connectionState = connectionState,
|
||||
deviceType = deviceType,
|
||||
isActive = isActive,
|
||||
isStoreForwardServer = node.num in storeForwardServers,
|
||||
)
|
||||
val isThisNode = remember(node) { ourNode?.num == node.num }
|
||||
if (!isThisNode) {
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.meshtastic.feature.node.detail.NodeManagementActions
|
||||
import org.meshtastic.feature.node.domain.usecase.GetFilteredNodesUseCase
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import org.meshtastic.proto.Config
|
||||
import org.meshtastic.sdk.CongestionLevel
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@KoinViewModel
|
||||
@@ -62,6 +63,10 @@ class NodeListViewModel(
|
||||
|
||||
val connectionState = serviceRepository.connectionState
|
||||
|
||||
val congestionLevel: StateFlow<CongestionLevel?> = serviceRepository.congestionLevel
|
||||
|
||||
val storeForwardServers: StateFlow<List<Int>> = serviceRepository.storeForwardServers
|
||||
|
||||
val deviceType: StateFlow<DeviceType?> =
|
||||
radioPrefs.devAddr
|
||||
.map { address -> address?.let { DeviceType.fromAddress(it) } }
|
||||
|
||||
Reference in New Issue
Block a user