TOTPRing.kt

2.1 KB · raw

1package com.peachpasswords.ui.components23import androidx.compose.foundation.Canvas4import androidx.compose.foundation.layout.size5import androidx.compose.runtime.Composable6import androidx.compose.runtime.getValue7import androidx.compose.runtime.produceState8import androidx.compose.ui.Modifier9import androidx.compose.ui.geometry.Offset10import androidx.compose.ui.geometry.Size11import androidx.compose.ui.graphics.StrokeCap12import androidx.compose.ui.graphics.drawscope.Stroke13import androidx.compose.ui.unit.Dp14import androidx.compose.ui.unit.dp15import com.peachpasswords.ui.theme.LocalPeachAccent16import com.peachpasswords.ui.theme.peachColors17import com.peachpasswords.vault.TOTPGenerator18import kotlinx.coroutines.delay1920/**21 * A small ring that sweeps once per TOTP period and turns breach-red in the22 * final 5 seconds. Port of the brief's TOTPRing.23 */24@Composable25fun TOTPRing(modifier: Modifier = Modifier, size: Dp = 28.dp) {26    val accent = LocalPeachAccent.current27    val progress by produceState(TOTPGenerator.periodProgress()) {28        while (true) { value = TOTPGenerator.periodProgress(); delay(100) }29    }30    val isHot = TOTPGenerator.remainingSeconds() <= 531    val track = peachColors.T332    val breach = peachColors.Breach3334    Canvas(modifier = modifier.size(size)) {35        val stroke = 2.dp.toPx()36        val radius = (this.size.minDimension - stroke) / 2f37        val center = Offset(this.size.width / 2f, this.size.height / 2f)38        drawCircle(color = track, radius = radius, style = Stroke(stroke))39        drawArc(40            color = if (isHot) breach else accent,41            startAngle = -90f,42            // Starts full at period start (progress=0), depletes to empty as the43            // code approaches expiry (progress=1) — the conventional countdown44            // direction, not "fills up as time passes."45            sweepAngle = 360f * (1f - progress),46            useCenter = false,47            style = Stroke(stroke, cap = StrokeCap.Round),48            topLeft = Offset(center.x - radius, center.y - radius),49            size = Size(radius * 2, radius * 2),50        )51    }52}