diff --git a/app/src/main/java/com/podometer/data/db/SensorWindow.kt b/app/src/main/java/com/podometer/data/db/SensorWindow.kt index 1341a57..21ba2b8 100644 --- a/app/src/main/java/com/podometer/data/db/SensorWindow.kt +++ b/app/src/main/java/com/podometer/data/db/SensorWindow.kt @@ -5,7 +5,7 @@ import androidx.room.Entity import androidx.room.PrimaryKey /** - * Room entity storing a single 5-second classifier window of raw sensor data. + * Room entity storing a single 30-second classifier window of raw sensor data. * * These windows are recorded continuously by [com.podometer.service.StepTrackingService] * and retained for 7 days. They enable retroactive recomputation of activity sessions diff --git a/app/src/main/java/com/podometer/domain/model/ActivitySession.kt b/app/src/main/java/com/podometer/domain/model/ActivitySession.kt index 83f68d8..2d2f5c7 100644 --- a/app/src/main/java/com/podometer/domain/model/ActivitySession.kt +++ b/app/src/main/java/com/podometer/domain/model/ActivitySession.kt @@ -16,6 +16,7 @@ package com.podometer.domain.model * this session. Used for override/reclassification. * @property isManualOverride True when the opening transition was manually * overridden by the user. + * @property stepCount Total steps detected during this session. */ data class ActivitySession( val activity: ActivityState, @@ -23,6 +24,7 @@ data class ActivitySession( val endTime: Long?, val startTransitionId: Int, val isManualOverride: Boolean, + val stepCount: Int = 0, ) /** diff --git a/app/src/main/java/com/podometer/domain/usecase/RecomputeActivitySessionsUseCase.kt b/app/src/main/java/com/podometer/domain/usecase/RecomputeActivitySessionsUseCase.kt index 7a78ce3..1f7e551 100644 --- a/app/src/main/java/com/podometer/domain/usecase/RecomputeActivitySessionsUseCase.kt +++ b/app/src/main/java/com/podometer/domain/usecase/RecomputeActivitySessionsUseCase.kt @@ -21,7 +21,7 @@ import javax.inject.Singleton * activity sessions for a given date. * * This enables retroactive recomputation when classifier parameters change — - * the raw 5-second windows are retained for 7 days and can be replayed at any + * the raw 30-second windows are retained for 7 days and can be replayed at any * time to generate updated session data. */ interface RecomputeActivitySessionsUseCase { @@ -94,7 +94,26 @@ class RecomputeActivitySessionsUseCaseImpl @Inject constructor( } } - return buildActivitySessions(transitions, nowMillis) + val sessions = buildActivitySessions(transitions, nowMillis) + return attachStepCounts(sessions, windows) + } + + /** + * Sums [SensorWindow.stepCount] for each session's time range and returns + * sessions with [ActivitySession.stepCount] populated. + */ + private fun attachStepCounts( + sessions: List, + windows: List, + ): List { + if (sessions.isEmpty() || windows.isEmpty()) return sessions + return sessions.map { session -> + val endTime = session.endTime ?: Long.MAX_VALUE + val steps = windows + .filter { it.timestamp in session.startTime until endTime } + .sumOf { it.stepCount } + session.copy(stepCount = steps) + } } } } diff --git a/app/src/main/java/com/podometer/ui/dashboard/ActivityLog.kt b/app/src/main/java/com/podometer/ui/dashboard/ActivityLog.kt index 341f1d3..ff6cf8b 100644 --- a/app/src/main/java/com/podometer/ui/dashboard/ActivityLog.kt +++ b/app/src/main/java/com/podometer/ui/dashboard/ActivityLog.kt @@ -284,9 +284,19 @@ private fun ActivityLogItem( text = durationText, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.weight(1f), ) + if (session.stepCount > 0) { + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.activity_session_steps, session.stepCount), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + + Spacer(modifier = Modifier.weight(1f)) + if (session.isManualOverride) { Spacer(modifier = Modifier.width(8.dp)) AssistChip( @@ -408,6 +418,7 @@ private fun PreviewActivityLogMultiple() { endTime = 10L * hour + 30L * 60_000L, startTransitionId = 1, isManualOverride = false, + stepCount = 1247, ), ActivitySession( activity = ActivityState.CYCLING, @@ -422,6 +433,7 @@ private fun PreviewActivityLogMultiple() { endTime = null, startTransitionId = 3, isManualOverride = false, + stepCount = 312, ), ) PodometerTheme { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d67d92d..af1548b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -70,6 +70,7 @@ ongoing + %1$d steps %1$s session, %2$s, %3$s Override options for session at %1$s diff --git a/app/src/test/java/com/podometer/domain/usecase/RecomputeActivitySessionsUseCaseTest.kt b/app/src/test/java/com/podometer/domain/usecase/RecomputeActivitySessionsUseCaseTest.kt index e0292d5..5a900e0 100644 --- a/app/src/test/java/com/podometer/domain/usecase/RecomputeActivitySessionsUseCaseTest.kt +++ b/app/src/test/java/com/podometer/domain/usecase/RecomputeActivitySessionsUseCaseTest.kt @@ -50,6 +50,27 @@ class RecomputeActivitySessionsUseCaseTest { assertEquals(ActivityState.WALKING, sessions[0].activity) } + @Test + fun `replayWindows attaches step counts to walking sessions`() { + val windows = (0 until 40).map { i -> + SensorWindow( + id = i.toLong(), + timestamp = 1_000_000L + i * 5_000L, + magnitudeVariance = 1.0, + stepFrequencyHz = 1.8, + stepCount = 9, + ) + } + + val sessions = RecomputeActivitySessionsUseCaseImpl.replayWindows( + windows = windows, + nowMillis = nowMillis, + ) + + assertTrue("Expected at least one session", sessions.isNotEmpty()) + assertTrue("Walking session should have steps > 0", sessions[0].stepCount > 0) + } + @Test fun `replayWindows detects cycling session from sustained cycling windows`() { // First, we need some STILL state, then transition to cycling.