feat: live time elapsed display on install steps
This commit is contained in:
parent
bd5bc39d41
commit
4b820e594e
7 changed files with 105 additions and 22 deletions
|
|
@ -53,8 +53,9 @@ abstract class StepRunner : KoinComponent {
|
|||
}
|
||||
|
||||
// Skip minimum run time when in dev mode
|
||||
if (!preferences.devMode && step.durationMs < MINIMUM_STEP_DELAY) {
|
||||
delay(MINIMUM_STEP_DELAY - step.durationMs)
|
||||
val duration = step.getDuration()
|
||||
if (!preferences.devMode && duration < MINIMUM_STEP_DELAY) {
|
||||
delay(MINIMUM_STEP_DELAY - duration)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import androidx.annotation.StringRes
|
|||
import androidx.compose.runtime.*
|
||||
import com.aliucord.manager.installer.steps.StepGroup
|
||||
import com.aliucord.manager.installer.steps.StepRunner
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.aliucord.manager.util.toPrecision
|
||||
import kotlinx.coroutines.*
|
||||
import org.koin.core.time.measureTimedValue
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
/**
|
||||
* A base install process step. Steps are single-use
|
||||
|
|
@ -44,12 +44,44 @@ abstract class Step {
|
|||
var progress by mutableFloatStateOf(-1f)
|
||||
protected set
|
||||
|
||||
private val durationSecs = mutableFloatStateOf(0f)
|
||||
private var startTime: Long? = null
|
||||
private var totalTimeMs: Long? = null
|
||||
|
||||
/**
|
||||
* The total execution time once this step has finished execution.
|
||||
* The total amount of time this step has/was executed for in milliseconds.
|
||||
* If this step has not started executing then it will return `0`.
|
||||
*/
|
||||
// TODO: make this a live value
|
||||
var durationMs by mutableIntStateOf(0)
|
||||
private set
|
||||
fun getDuration(): Long {
|
||||
// Step hasn't started executing
|
||||
val startTime = startTime ?: return 0
|
||||
|
||||
// Step already finished executing
|
||||
totalTimeMs?.let { return it }
|
||||
|
||||
return System.currentTimeMillis() - startTime
|
||||
}
|
||||
|
||||
/**
|
||||
* The live execution time of this step in seconds.
|
||||
* The value is clamped to a resolution of 10ms updated every 50ms.
|
||||
*/
|
||||
@Composable
|
||||
fun collectDurationAsState(): State<Float> {
|
||||
if (state.isFinished)
|
||||
return durationSecs
|
||||
|
||||
LaunchedEffect(state) {
|
||||
while (true) {
|
||||
durationSecs.floatValue = (getDuration() / 1000.0)
|
||||
.toPrecision(2).toFloat()
|
||||
|
||||
delay(50)
|
||||
}
|
||||
}
|
||||
|
||||
return durationSecs
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin wrapper over [execute] but handling errors.
|
||||
|
|
@ -60,6 +92,7 @@ abstract class Step {
|
|||
throw IllegalStateException("Cannot execute a step that has already started")
|
||||
|
||||
state = StepState.Running
|
||||
startTime = System.currentTimeMillis()
|
||||
|
||||
// Execute this steps logic while timing it
|
||||
val (error, executionTimeMs) = measureTimedValue {
|
||||
|
|
@ -78,7 +111,9 @@ abstract class Step {
|
|||
}
|
||||
}
|
||||
|
||||
durationMs = executionTimeMs.roundToInt()
|
||||
totalTimeMs = executionTimeMs.roundToLong()
|
||||
durationSecs.floatValue = executionTimeMs.toFloat() / 1000f
|
||||
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ enum class StepState {
|
|||
Skipped;
|
||||
|
||||
val isFinished: Boolean
|
||||
get() = this == Success || this == Error || this == Skipped
|
||||
get() = this == Success || this == Skipped || this == Error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,16 @@ fun StepGroupCard(
|
|||
}
|
||||
}
|
||||
|
||||
val totalSeconds = remember(groupState.isFinished) {
|
||||
if (!groupState.isFinished) {
|
||||
0f
|
||||
} else {
|
||||
subSteps
|
||||
.sumOf { step -> step.getDuration() }
|
||||
.div(1000f)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(groupState) {
|
||||
if (groupState != StepState.Pending)
|
||||
onExpand()
|
||||
|
|
@ -68,9 +78,9 @@ fun StepGroupCard(
|
|||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
if (groupState.isFinished) Text(
|
||||
"%.2fs".format(subSteps.sumOf { it.durationMs } / 1000f),
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
TimeElapsed(
|
||||
enabled = groupState.isFinished,
|
||||
seconds = totalSeconds,
|
||||
)
|
||||
|
||||
if (isExpanded) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.base.StepState
|
||||
|
||||
@Composable
|
||||
fun StepItem(
|
||||
|
|
@ -36,13 +37,9 @@ fun StepItem(
|
|||
modifier = Modifier.weight(1f, true),
|
||||
)
|
||||
|
||||
// TODO: live step duration counter
|
||||
if (step.state.isFinished) {
|
||||
Text(
|
||||
text = "%.2fs".format(step.durationMs / 1000f),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
TimeElapsed(
|
||||
enabled = step.state != StepState.Pending,
|
||||
seconds = step.collectDurationAsState().value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package com.aliucord.manager.ui.screens.install.components
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun TimeElapsed(
|
||||
seconds: Float,
|
||||
enabled: Boolean = true,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = enabled,
|
||||
enter = fadeIn(),
|
||||
exit = ExitTransition.None,
|
||||
label = "TimeElapsed Visibility"
|
||||
) {
|
||||
Text(
|
||||
text = "%.2fs".format(seconds),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
maxLines = 1,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
12
app/src/main/kotlin/com/aliucord/manager/util/Utils.kt
Normal file
12
app/src/main/kotlin/com/aliucord/manager/util/Utils.kt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package com.aliucord.manager.util
|
||||
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.truncate
|
||||
|
||||
/**
|
||||
* Truncates this value to a specific number of [decimals] digits.
|
||||
*/
|
||||
fun Double.toPrecision(decimals: Int): Double {
|
||||
val multiplier = 10.0.pow(decimals)
|
||||
return truncate(this * multiplier) / multiplier
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue