瀏覽代碼

Add autofill option

Lee Morgan 2 周之前
父節點
當前提交
c369158596

+ 16 - 0
app/src/main/java/com/example/torus/model/DistanceUnit.kt

@@ -19,3 +19,19 @@ fun parseDurationSeconds(hours: String, minutes: String, seconds: String): Int {
     val secondsValue = seconds.toIntOrNull() ?: 0
     return hoursValue * 3600 + minutesValue * 60 + secondsValue
 }
+
+fun formatAmountFromMeters(meters: Double, unit: DistanceUnit): String {
+    val amount = meters / unit.metersPerUnit
+    val rounded = kotlin.math.round(amount * 1_000_000.0) / 1_000_000.0
+    return if (rounded % 1.0 == 0.0) {
+        rounded.toLong().toString()
+    } else {
+        String.format(java.util.Locale.US, "%.6f", rounded).trimEnd('0').trimEnd('.')
+    }
+}
+
+fun durationHours(totalSeconds: Int): Int = totalSeconds / 3600
+
+fun durationMinutes(totalSeconds: Int): Int = (totalSeconds % 3600) / 60
+
+fun durationSecondsPart(totalSeconds: Int): Int = totalSeconds % 60

+ 9 - 0
app/src/main/java/com/example/torus/model/LastExerciseLog.kt

@@ -12,6 +12,15 @@ data class LastWeightSet(
     val reps: String,
 )
 
+fun LastExerciseLog.hasRecordedData(): Boolean {
+    return when (type) {
+        ExerciseType.Weights -> sets.any { set ->
+            set.weight.isNotBlank() || set.reps.isNotBlank()
+        }
+        ExerciseType.Distance -> distanceMeters > 0.0 || durationSeconds > 0
+    }
+}
+
 fun formatDistanceMeters(meters: Double): String {
     return if (meters >= 1000.0) {
         String.format(java.util.Locale.US, "%.2f km", meters / 1000.0)

+ 73 - 8
app/src/main/java/com/example/torus/ui/StartWorkoutScreen.kt

@@ -3,8 +3,10 @@ package com.example.torus.ui
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -49,8 +51,13 @@ import com.example.torus.id.UuidV7
 import com.example.torus.model.DistanceUnit
 import com.example.torus.model.ExerciseType
 import com.example.torus.model.LastExerciseLog
+import com.example.torus.model.durationHours
+import com.example.torus.model.durationMinutes
+import com.example.torus.model.durationSecondsPart
+import com.example.torus.model.formatAmountFromMeters
 import com.example.torus.model.formatDistanceMeters
 import com.example.torus.model.formatDurationSeconds
+import com.example.torus.model.hasRecordedData
 import com.example.torus.model.parseDistanceMeters
 import com.example.torus.model.parseDurationSeconds
 import com.example.torus.model.SavedExercise
@@ -202,8 +209,43 @@ private fun StartWorkoutContent(
                     color = MaterialTheme.colorScheme.onSurfaceVariant,
                     style = MaterialTheme.typography.labelLarge
                 )
-                lastLogs[exercise.id]?.let { log ->
-                    LastTimeCard(log = log)
+                lastLogs[exercise.id]?.takeIf { it.hasRecordedData() }?.let { log ->
+                    LastTimeCard(
+                        log = log,
+                        onUseLastTime = {
+                            when (log.type) {
+                                ExerciseType.Weights -> {
+                                    val sets = setsByExercise.getOrPut(exercise.id) {
+                                        defaultWeightSets()
+                                    }
+                                    sets.clear()
+                                    if (log.sets.isEmpty()) {
+                                        sets.addAll(defaultWeightSets())
+                                    } else {
+                                        log.sets.forEach { lastSet ->
+                                            sets += WeightSetDraft(
+                                                id = UuidV7.generateString(),
+                                                weight = lastSet.weight,
+                                                reps = lastSet.reps
+                                            )
+                                        }
+                                    }
+                                }
+                                ExerciseType.Distance -> {
+                                    val current = distanceByExercise[exercise.id] ?: DistanceDraft()
+                                    distanceByExercise[exercise.id] = current.copy(
+                                        amount = formatAmountFromMeters(
+                                            log.distanceMeters,
+                                            current.unit
+                                        ),
+                                        hours = durationHours(log.durationSeconds).toString(),
+                                        minutes = durationMinutes(log.durationSeconds).toString(),
+                                        seconds = durationSecondsPart(log.durationSeconds).toString()
+                                    )
+                                }
+                            }
+                        }
+                    )
                 }
                 when (exercise.type) {
                     ExerciseType.Weights -> {
@@ -286,7 +328,10 @@ private fun StartWorkoutContent(
 }
 
 @Composable
-private fun LastTimeCard(log: LastExerciseLog) {
+private fun LastTimeCard(
+    log: LastExerciseLog,
+    onUseLastTime: () -> Unit
+) {
     val shape = RoundedCornerShape(14.dp)
     Column(
         modifier = Modifier
@@ -296,11 +341,31 @@ private fun LastTimeCard(log: LastExerciseLog) {
             .padding(16.dp),
         verticalArrangement = Arrangement.spacedBy(8.dp)
     ) {
-        Text(
-            text = stringResource(R.string.last_time),
-            color = Sand,
-            style = MaterialTheme.typography.titleSmall
-        )
+        Row(
+            modifier = Modifier.fillMaxWidth(),
+            horizontalArrangement = Arrangement.SpaceBetween,
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            Text(
+                text = stringResource(R.string.last_time),
+                color = Sand,
+                style = MaterialTheme.typography.titleSmall
+            )
+            OutlinedButton(
+                onClick = onUseLastTime,
+                shape = RoundedCornerShape(50),
+                contentPadding = PaddingValues(horizontal = 14.dp, vertical = 6.dp),
+                colors = ButtonDefaults.outlinedButtonColors(
+                    contentColor = MaterialTheme.colorScheme.onBackground
+                ),
+                border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
+            ) {
+                Text(
+                    text = stringResource(R.string.autofill),
+                    style = MaterialTheme.typography.labelLarge
+                )
+            }
+        }
         when (log.type) {
             ExerciseType.Weights -> {
                 if (log.sets.isEmpty()) {

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -44,6 +44,7 @@
     <string name="exercise_complete">Complete</string>
     <string name="exercise_incomplete">Incomplete</string>
     <string name="last_time">Last time</string>
+    <string name="autofill">Autofill</string>
     <string name="last_set">Set %1$d: %2$s × %3$s</string>
     <string name="last_empty_value">—</string>
     <string name="last_distance">Distance: %1$s</string>

+ 8 - 0
app/src/test/java/com/example/torus/DistanceConversionTest.kt

@@ -1,6 +1,7 @@
 package com.example.torus
 
 import com.example.torus.model.DistanceUnit
+import com.example.torus.model.formatAmountFromMeters
 import com.example.torus.model.parseDistanceMeters
 import com.example.torus.model.parseDurationSeconds
 import org.junit.Assert.assertEquals
@@ -24,4 +25,11 @@ class DistanceConversionTest {
         assertEquals(150, parseDurationSeconds("", "2", "30"))
         assertEquals(45, parseDurationSeconds("", "", "45"))
     }
+
+    @Test
+    fun formatAmountFromMeters_usesSelectedUnit() {
+        assertEquals("5", formatAmountFromMeters(5000.0, DistanceUnit.Kilometers))
+        assertEquals("1", formatAmountFromMeters(1609.344, DistanceUnit.Miles))
+        assertEquals("12.5", formatAmountFromMeters(12.5, DistanceUnit.Meters))
+    }
 }

+ 32 - 0
app/src/test/java/com/example/torus/LastExerciseFormatTest.kt

@@ -1,7 +1,13 @@
 package com.example.torus
 
+import com.example.torus.model.ExerciseType
+import com.example.torus.model.LastExerciseLog
+import com.example.torus.model.LastWeightSet
 import com.example.torus.model.formatDistanceMeters
 import com.example.torus.model.formatDurationSeconds
+import com.example.torus.model.hasRecordedData
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Assert.assertEquals
 import org.junit.Test
 
@@ -17,4 +23,30 @@ class LastExerciseFormatTest {
         assertEquals("2:03", formatDurationSeconds(123))
         assertEquals("1:02:03", formatDurationSeconds(3723))
     }
+
+    @Test
+    fun hasRecordedData_isFalseWhenLastSessionWasEmpty() {
+        assertFalse(
+            LastExerciseLog(
+                type = ExerciseType.Weights,
+                sets = listOf(LastWeightSet(weight = "", reps = ""))
+            ).hasRecordedData()
+        )
+        assertFalse(
+            LastExerciseLog(type = ExerciseType.Distance).hasRecordedData()
+        )
+        assertTrue(
+            LastExerciseLog(
+                type = ExerciseType.Weights,
+                sets = listOf(LastWeightSet(weight = "135", reps = "8"))
+            ).hasRecordedData()
+        )
+        assertTrue(
+            LastExerciseLog(
+                type = ExerciseType.Distance,
+                distanceMeters = 5000.0,
+                durationSeconds = 1500
+            ).hasRecordedData()
+        )
+    }
 }