mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Feat request neighbours (#3709)
Signed-off-by: Dane Evans <dane@goneepic.com>
This commit is contained in:
parent
3e3dfe08e6
commit
d33229c50f
14 changed files with 375 additions and 10 deletions
|
|
@ -22,6 +22,7 @@ import androidx.compose.animation.core.tween
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Route
|
||||
import androidx.compose.material.icons.twotone.Mediation
|
||||
import androidx.compose.material3.CircularWavyProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -30,16 +31,19 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.request_neighbor_info
|
||||
import org.meshtastic.core.strings.traceroute
|
||||
import org.meshtastic.core.ui.component.BasicListItem
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
private const val COOL_DOWN_TIME_MS = 30000L
|
||||
private const val REQUEST_NEIGHBORS_COOL_DOWN_TIME_MS = 180000L // 3 minutes
|
||||
|
||||
@Composable
|
||||
fun TracerouteButton(
|
||||
|
|
@ -61,20 +65,42 @@ fun TracerouteButton(
|
|||
}
|
||||
}
|
||||
|
||||
TracerouteButton(text = text, progress = progress.value, onClick = onClick)
|
||||
CooldownButton(text = text, leadingIcon = Icons.Default.Route, progress = progress.value, onClick = onClick)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RequestNeighborsButton(
|
||||
text: String = stringResource(Res.string.request_neighbor_info),
|
||||
lastRequestNeighborsTime: Long?,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val progress = remember { Animatable(0f) }
|
||||
|
||||
LaunchedEffect(lastRequestNeighborsTime) {
|
||||
val timeSinceLast = System.currentTimeMillis() - (lastRequestNeighborsTime ?: 0)
|
||||
if (timeSinceLast < REQUEST_NEIGHBORS_COOL_DOWN_TIME_MS) {
|
||||
val remainingTime = REQUEST_NEIGHBORS_COOL_DOWN_TIME_MS - timeSinceLast
|
||||
progress.snapTo(remainingTime / REQUEST_NEIGHBORS_COOL_DOWN_TIME_MS.toFloat())
|
||||
progress.animateTo(
|
||||
targetValue = 0f,
|
||||
animationSpec = tween(durationMillis = remainingTime.toInt(), easing = { it }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
CooldownButton(text = text, leadingIcon = Icons.TwoTone.Mediation, progress = progress.value, onClick = onClick)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
private fun TracerouteButton(text: String, progress: Float, onClick: () -> Unit) {
|
||||
private fun CooldownButton(text: String, leadingIcon: ImageVector, progress: Float, onClick: () -> Unit) {
|
||||
val isCoolingDown = progress > 0f
|
||||
|
||||
val stroke = Stroke(width = with(LocalDensity.current) { 2.dp.toPx() }, cap = StrokeCap.Round)
|
||||
|
||||
BasicListItem(
|
||||
text = text,
|
||||
enabled = !isCoolingDown,
|
||||
leadingIcon = Icons.Default.Route,
|
||||
leadingIcon = leadingIcon,
|
||||
trailingContent = {
|
||||
if (isCoolingDown) {
|
||||
CircularWavyProgressIndicator(
|
||||
|
|
@ -97,5 +123,5 @@ private fun TracerouteButton(text: String, progress: Float, onClick: () -> Unit)
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun TracerouteButtonPreview() {
|
||||
AppTheme { TracerouteButton(text = "Traceroute", progress = .6f, onClick = {}) }
|
||||
AppTheme { CooldownButton(text = "Traceroute", leadingIcon = Icons.Default.Route, progress = .6f, onClick = {}) }
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ import org.meshtastic.feature.node.model.NodeDetailAction
|
|||
fun DeviceActions(
|
||||
node: Node,
|
||||
lastTracerouteTime: Long?,
|
||||
lastRequestNeighborsTime: Long?,
|
||||
onAction: (NodeDetailAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isLocal: Boolean = false,
|
||||
|
|
@ -81,7 +82,12 @@ fun DeviceActions(
|
|||
)
|
||||
if (!isLocal) {
|
||||
InsetDivider()
|
||||
RemoteDeviceActions(node = node, lastTracerouteTime = lastTracerouteTime, onAction = onAction)
|
||||
RemoteDeviceActions(
|
||||
node = node,
|
||||
lastTracerouteTime = lastTracerouteTime,
|
||||
lastRequestNeighborsTime = lastRequestNeighborsTime,
|
||||
onAction = onAction,
|
||||
)
|
||||
}
|
||||
|
||||
InsetDivider()
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ sealed class NodeMenuAction {
|
|||
|
||||
data class RequestUserInfo(val node: Node) : NodeMenuAction()
|
||||
|
||||
data class RequestNeighborInfo(val node: Node) : NodeMenuAction()
|
||||
|
||||
data class RequestPosition(val node: Node) : NodeMenuAction()
|
||||
|
||||
data class TraceRoute(val node: Node) : NodeMenuAction()
|
||||
|
|
|
|||
|
|
@ -32,7 +32,12 @@ import org.meshtastic.feature.node.model.NodeDetailAction
|
|||
import org.meshtastic.feature.node.model.isEffectivelyUnmessageable
|
||||
|
||||
@Composable
|
||||
internal fun RemoteDeviceActions(node: Node, lastTracerouteTime: Long?, onAction: (NodeDetailAction) -> Unit) {
|
||||
internal fun RemoteDeviceActions(
|
||||
node: Node,
|
||||
lastTracerouteTime: Long?,
|
||||
lastRequestNeighborsTime: Long?,
|
||||
onAction: (NodeDetailAction) -> Unit,
|
||||
) {
|
||||
if (!node.isEffectivelyUnmessageable) {
|
||||
ListItem(
|
||||
text = stringResource(Res.string.direct_message),
|
||||
|
|
@ -57,4 +62,8 @@ internal fun RemoteDeviceActions(node: Node, lastTracerouteTime: Long?, onAction
|
|||
lastTracerouteTime = lastTracerouteTime,
|
||||
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.TraceRoute(node))) },
|
||||
)
|
||||
RequestNeighborsButton(
|
||||
lastRequestNeighborsTime = lastRequestNeighborsTime,
|
||||
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.RequestNeighborInfo(node))) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ fun NodeDetailContent(
|
|||
ourNode: Node?,
|
||||
metricsState: MetricsState,
|
||||
lastTracerouteTime: Long?,
|
||||
lastRequestNeighborsTime: Long?,
|
||||
availableLogs: Set<LogsType>,
|
||||
onAction: (NodeDetailAction) -> Unit,
|
||||
onSaveNotes: (nodeNum: Int, notes: String) -> Unit,
|
||||
|
|
@ -87,6 +88,7 @@ fun NodeDetailContent(
|
|||
NodeDetailList(
|
||||
node = node,
|
||||
lastTracerouteTime = lastTracerouteTime,
|
||||
lastRequestNeighborsTime = lastRequestNeighborsTime,
|
||||
ourNode = ourNode,
|
||||
metricsState = metricsState,
|
||||
onAction = { action ->
|
||||
|
|
@ -108,6 +110,7 @@ fun NodeDetailContent(
|
|||
fun NodeDetailList(
|
||||
node: Node,
|
||||
lastTracerouteTime: Long?,
|
||||
lastRequestNeighborsTime: Long?,
|
||||
ourNode: Node?,
|
||||
metricsState: MetricsState,
|
||||
onAction: (NodeDetailAction) -> Unit,
|
||||
|
|
@ -165,6 +168,7 @@ fun NodeDetailList(
|
|||
DeviceActions(
|
||||
isLocal = metricsState.isLocal,
|
||||
lastTracerouteTime = lastTracerouteTime,
|
||||
lastRequestNeighborsTime = lastRequestNeighborsTime,
|
||||
node = node,
|
||||
onAction = onAction,
|
||||
)
|
||||
|
|
@ -263,6 +267,7 @@ private fun NodeDetailsPreview(@PreviewParameter(NodePreviewParameterProvider::c
|
|||
node = node,
|
||||
ourNode = node,
|
||||
lastTracerouteTime = null,
|
||||
lastRequestNeighborsTime = null,
|
||||
metricsState = MetricsState.Companion.Empty,
|
||||
availableLogs = emptySet(),
|
||||
onAction = {},
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ fun NodeDetailScreen(
|
|||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val environmentState by viewModel.environmentState.collectAsStateWithLifecycle()
|
||||
val lastTracerouteTime by nodeDetailViewModel.lastTraceRouteTime.collectAsStateWithLifecycle()
|
||||
val lastRequestNeighborsTime by nodeDetailViewModel.lastRequestNeighborsTime.collectAsStateWithLifecycle()
|
||||
val ourNode by nodeDetailViewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
|
||||
val availableLogs by
|
||||
|
|
@ -100,6 +101,7 @@ fun NodeDetailScreen(
|
|||
ourNode = ourNode,
|
||||
metricsState = state,
|
||||
lastTracerouteTime = lastTracerouteTime,
|
||||
lastRequestNeighborsTime = lastRequestNeighborsTime,
|
||||
availableLogs = availableLogs,
|
||||
onAction = { action ->
|
||||
handleNodeAction(
|
||||
|
|
|
|||
|
|
@ -48,12 +48,19 @@ constructor(
|
|||
private val _lastTraceRouteTime = MutableStateFlow<Long?>(null)
|
||||
val lastTraceRouteTime: StateFlow<Long?> = _lastTraceRouteTime.asStateFlow()
|
||||
|
||||
private val _lastRequestNeighborsTime = MutableStateFlow<Long?>(null)
|
||||
val lastRequestNeighborsTime: StateFlow<Long?> = _lastRequestNeighborsTime.asStateFlow()
|
||||
|
||||
fun handleNodeMenuAction(action: NodeMenuAction) {
|
||||
when (action) {
|
||||
is NodeMenuAction.Remove -> removeNode(action.node.num)
|
||||
is NodeMenuAction.Ignore -> ignoreNode(action.node)
|
||||
is NodeMenuAction.Favorite -> favoriteNode(action.node)
|
||||
is NodeMenuAction.RequestUserInfo -> requestUserInfo(action.node.num)
|
||||
is NodeMenuAction.RequestNeighborInfo -> {
|
||||
requestNeighborInfo(action.node.num)
|
||||
_lastRequestNeighborsTime.value = System.currentTimeMillis()
|
||||
}
|
||||
is NodeMenuAction.RequestPosition -> requestPosition(action.node.num)
|
||||
is NodeMenuAction.TraceRoute -> {
|
||||
requestTraceroute(action.node.num)
|
||||
|
|
@ -110,6 +117,16 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun requestNeighborInfo(destNum: Int) {
|
||||
Timber.i("Requesting NeighborInfo for '$destNum'")
|
||||
try {
|
||||
val packetId = serviceRepository.meshService?.packetId ?: return
|
||||
serviceRepository.meshService?.requestNeighborInfo(packetId, destNum)
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e("Request NeighborInfo error: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestPosition(destNum: Int, position: Position = Position(0.0, 0.0, 0)) {
|
||||
Timber.i("Requesting position for '$destNum'")
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue