mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-27 18:21:58 -04:00
refactor: simplify traceroute tracking and unify cooldown button logic (#4699)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
@@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.meshtastic.feature.node.component
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -26,11 +24,15 @@ import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.OutlinedIconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.Refresh
|
||||
@@ -43,85 +45,69 @@ internal const val REQUEST_NEIGHBORS_COOL_DOWN_TIME_MS = 180000L // 3 minutes
|
||||
fun CooldownIconButton(
|
||||
onClick: () -> Unit,
|
||||
cooldownTimestamp: Long?,
|
||||
modifier: Modifier = Modifier,
|
||||
cooldownDuration: Long = COOL_DOWN_TIME_MS,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val progress = remember { Animatable(0f) }
|
||||
|
||||
LaunchedEffect(cooldownTimestamp) {
|
||||
if (cooldownTimestamp == null) {
|
||||
progress.snapTo(0f)
|
||||
return@LaunchedEffect
|
||||
}
|
||||
val timeSinceLast = nowMillis - cooldownTimestamp
|
||||
if (timeSinceLast < cooldownDuration) {
|
||||
val remainingTime = cooldownDuration - timeSinceLast
|
||||
progress.snapTo(remainingTime / cooldownDuration.toFloat())
|
||||
progress.animateTo(
|
||||
targetValue = 0f,
|
||||
animationSpec = tween(durationMillis = remainingTime.toInt(), easing = { it }),
|
||||
)
|
||||
} else {
|
||||
progress.snapTo(0f)
|
||||
}
|
||||
}
|
||||
|
||||
val isCoolingDown = progress.value > 0f
|
||||
|
||||
IconButton(
|
||||
onClick = { if (!isCoolingDown) onClick() },
|
||||
enabled = !isCoolingDown,
|
||||
colors = IconButtonDefaults.iconButtonColors(),
|
||||
) {
|
||||
if (isCoolingDown) {
|
||||
CircularProgressIndicator(
|
||||
progress = { progress.value },
|
||||
modifier = Modifier.size(24.dp),
|
||||
strokeCap = StrokeCap.Round,
|
||||
)
|
||||
} else {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
) = CooldownBaseButton(
|
||||
onClick = onClick,
|
||||
cooldownTimestamp = cooldownTimestamp,
|
||||
cooldownDuration = cooldownDuration,
|
||||
modifier = modifier,
|
||||
outlined = false,
|
||||
content = content,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun CooldownOutlinedIconButton(
|
||||
onClick: () -> Unit,
|
||||
cooldownTimestamp: Long?,
|
||||
modifier: Modifier = Modifier,
|
||||
cooldownDuration: Long = COOL_DOWN_TIME_MS,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val progress = remember { Animatable(0f) }
|
||||
CooldownBaseButton(
|
||||
onClick = onClick,
|
||||
cooldownTimestamp = cooldownTimestamp,
|
||||
cooldownDuration = cooldownDuration,
|
||||
modifier = modifier,
|
||||
outlined = true,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(cooldownTimestamp) {
|
||||
if (cooldownTimestamp == null) {
|
||||
progress.snapTo(0f)
|
||||
return@LaunchedEffect
|
||||
}
|
||||
val timeSinceLast = nowMillis - cooldownTimestamp
|
||||
if (timeSinceLast < cooldownDuration) {
|
||||
val remainingTime = cooldownDuration - timeSinceLast
|
||||
progress.snapTo(remainingTime / cooldownDuration.toFloat())
|
||||
progress.animateTo(
|
||||
targetValue = 0f,
|
||||
animationSpec = tween(durationMillis = remainingTime.toInt(), easing = { it }),
|
||||
)
|
||||
} else {
|
||||
progress.snapTo(0f)
|
||||
private const val TICK = 100L
|
||||
|
||||
@Composable
|
||||
private fun CooldownBaseButton(
|
||||
onClick: () -> Unit,
|
||||
cooldownTimestamp: Long?,
|
||||
cooldownDuration: Long,
|
||||
modifier: Modifier = Modifier,
|
||||
outlined: Boolean = false,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
var progress by remember { mutableStateOf(0f) }
|
||||
var isCoolingDown by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(cooldownTimestamp, cooldownDuration) {
|
||||
val endTime = (cooldownTimestamp ?: 0L) + cooldownDuration
|
||||
isCoolingDown = nowMillis < endTime
|
||||
|
||||
while (isCoolingDown) {
|
||||
val remainingTime = endTime - nowMillis
|
||||
if (remainingTime <= 0) break
|
||||
progress = (remainingTime.toFloat() / cooldownDuration).coerceIn(0f, 1f)
|
||||
delay(TICK)
|
||||
isCoolingDown = nowMillis < endTime
|
||||
}
|
||||
progress = 0f
|
||||
isCoolingDown = false
|
||||
}
|
||||
|
||||
val isCoolingDown = progress.value > 0f
|
||||
|
||||
OutlinedIconButton(
|
||||
onClick = { if (!isCoolingDown) onClick() },
|
||||
enabled = !isCoolingDown,
|
||||
colors = IconButtonDefaults.outlinedIconButtonColors(),
|
||||
) {
|
||||
val buttonContent: @Composable () -> Unit = {
|
||||
if (isCoolingDown) {
|
||||
CircularProgressIndicator(
|
||||
progress = { progress.value },
|
||||
progress = { progress },
|
||||
modifier = Modifier.size(24.dp),
|
||||
strokeCap = StrokeCap.Round,
|
||||
)
|
||||
@@ -129,6 +115,24 @@ fun CooldownOutlinedIconButton(
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
if (outlined) {
|
||||
OutlinedIconButton(
|
||||
onClick = onClick,
|
||||
enabled = !isCoolingDown,
|
||||
colors = IconButtonDefaults.outlinedIconButtonColors(),
|
||||
modifier = modifier,
|
||||
content = buttonContent,
|
||||
)
|
||||
} else {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
enabled = !isCoolingDown,
|
||||
colors = IconButtonDefaults.iconButtonColors(),
|
||||
modifier = modifier,
|
||||
content = buttonContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
|
||||
@@ -58,8 +58,8 @@ class NodeRequestActions @Inject constructor(private val radioController: RadioC
|
||||
private val _effects = MutableSharedFlow<NodeRequestEffect>()
|
||||
val effects: SharedFlow<NodeRequestEffect> = _effects.asSharedFlow()
|
||||
|
||||
private val _lastTracerouteTimes = MutableStateFlow<Map<Int, Long>>(emptyMap())
|
||||
val lastTracerouteTimes: StateFlow<Map<Int, Long>> = _lastTracerouteTimes.asStateFlow()
|
||||
private val _lastTracerouteTime = MutableStateFlow<Long?>(null)
|
||||
val lastTracerouteTime: StateFlow<Long?> = _lastTracerouteTime.asStateFlow()
|
||||
|
||||
private val _lastRequestNeighborTimes = MutableStateFlow<Map<Int, Long>>(emptyMap())
|
||||
val lastRequestNeighborTimes: StateFlow<Map<Int, Long>> = _lastRequestNeighborTimes.asStateFlow()
|
||||
@@ -135,7 +135,7 @@ class NodeRequestActions @Inject constructor(private val radioController: RadioC
|
||||
Logger.i { "Requesting traceroute for '$destNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.requestTraceroute(packetId, destNum)
|
||||
_lastTracerouteTimes.update { it + (destNum to nowMillis) }
|
||||
_lastTracerouteTime.value = nowMillis
|
||||
_effects.emit(
|
||||
NodeRequestEffect.ShowFeedback(
|
||||
UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName),
|
||||
|
||||
@@ -123,7 +123,7 @@ constructor(
|
||||
.onStart { emit(null) },
|
||||
firmwareReleaseRepository.stableRelease,
|
||||
firmwareReleaseRepository.alphaRelease,
|
||||
nodeRequestActions.lastTracerouteTimes.map { it[nodeId] },
|
||||
nodeRequestActions.lastTracerouteTime,
|
||||
nodeRequestActions.lastRequestNeighborTimes.map { it[nodeId] },
|
||||
) { edition, stable, alpha, trTime, niTime ->
|
||||
MetadataGroup(edition = edition, stable = stable, alpha = alpha, trTime = trTime, niTime = niTime)
|
||||
|
||||
@@ -185,9 +185,7 @@ constructor(
|
||||
|
||||
val effects: SharedFlow<NodeRequestEffect> = nodeRequestActions.effects
|
||||
|
||||
val lastTraceRouteTime: StateFlow<Long?> =
|
||||
combine(nodeRequestActions.lastTracerouteTimes, activeNodeId) { map, id -> id?.let { map[it] } }
|
||||
.stateInWhileSubscribed(null)
|
||||
val lastTraceRouteTime: StateFlow<Long?> = nodeRequestActions.lastTracerouteTime
|
||||
|
||||
val lastRequestNeighborsTime: StateFlow<Long?> =
|
||||
combine(nodeRequestActions.lastRequestNeighborTimes, activeNodeId) { map, id -> id?.let { map[it] } }
|
||||
|
||||
Reference in New Issue
Block a user