From 4b820e594ed63d2874708fbad85374a07fd1ff80 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Sun, 10 Mar 2024 22:41:09 -0700 Subject: [PATCH] feat: live time elapsed display on install steps --- .../manager/installer/steps/StepRunner.kt | 5 +- .../manager/installer/steps/base/Step.kt | 51 ++++++++++++++++--- .../manager/installer/steps/base/StepState.kt | 2 +- .../install/components/StepGroupCard.kt | 16 ++++-- .../ui/screens/install/components/StepItem.kt | 13 ++--- .../screens/install/components/TimeElapsed.kt | 28 ++++++++++ .../kotlin/com/aliucord/manager/util/Utils.kt | 12 +++++ 7 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/TimeElapsed.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/util/Utils.kt diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/StepRunner.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/StepRunner.kt index 929368e..587b42e 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/StepRunner.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/StepRunner.kt @@ -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) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/base/Step.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/base/Step.kt index 1b870d1..f533504 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/base/Step.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/base/Step.kt @@ -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 { + 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 } } diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/base/StepState.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/base/StepState.kt index eac39e7..4f13336 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/base/StepState.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/base/StepState.kt @@ -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 } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepGroupCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepGroupCard.kt index 39b99fd..42abcdc 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepGroupCard.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepGroupCard.kt @@ -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) { diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepItem.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepItem.kt index d2f82f8..49971f9 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepItem.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepItem.kt @@ -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, + ) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/TimeElapsed.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/TimeElapsed.kt new file mode 100644 index 0000000..b19294d --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/TimeElapsed.kt @@ -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, + ) + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/util/Utils.kt b/app/src/main/kotlin/com/aliucord/manager/util/Utils.kt new file mode 100644 index 0000000..ba716ee --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/util/Utils.kt @@ -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 +}