Skip to content

Latest commit

 

History

History
108 lines (82 loc) · 3.91 KB

File metadata and controls

108 lines (82 loc) · 3.91 KB

Workout Timing Fix - Technical Explanation

Problem Summary

Activity uploads to Intervals.icu were showing incorrect durations - typically 2 minutes shorter than the actual workout time displayed in the app.

Root Cause Analysis

Previous Implementation (Dual Timer System)

The workout system maintained two independent time tracking mechanisms:

  1. Wall-Clock Timer (elapsedSeconds + _previouslyElapsedTime)

    • Used DateTime.now() to track actual elapsed time
    • Accumulated time across pause/resume cycles
    • Included system overhead and timing jitter
  2. Progress Timer (_workoutProgressTime)

    • Incremented by exactly 0.1 seconds per timer tick
    • Represented the workout's intended progress
    • Not affected by system overhead
  3. Track Points (stored in FIT/GPX files)

    • Used DateTime.now() for timestamps
    • Duration calculated from first to last track point timestamps
    • Reflected wall-clock time with overhead

The Issue

  • App UI showed elapsedSeconds (wall-clock time)
  • Exported files duration calculated from track point timestamps (wall-clock time)
  • Timer overhead accumulated over time (e.g., ~2 mins for 30-min workout)
  • Result: Mismatch between displayed time and exported file duration

Solution

New Implementation (Single Source of Truth)

Changed to use _workoutProgressTime as the single authoritative time source:

  1. Eliminated elapsedSeconds variable

    • Now a computed getter: int get elapsedSeconds => _workoutProgressTime.round()
  2. Eliminated _previouslyElapsedTime tracking

    • No longer needed since we don't track wall-clock segments
  3. Track Point Timestamps

    • Changed from: DateTime.now()
    • Changed to: _workoutStartTime + Duration(milliseconds: _workoutProgressTime * 1000)
    • Track points now based on workout progress, not wall clock
  4. Benefits

    • UI elapsed time = workout progress time
    • Track point duration = workout progress time
    • Exported file duration = workout progress time
    • Perfect consistency - no more drift or discrepancies

Code Changes

workout_controller.dart

  • Removed elapsedSeconds as a variable, added as getter
  • Removed _previouslyElapsedTime variable
  • Changed _lastTrackPointTime from DateTime? to double (workout seconds)
  • Updated track point creation to use calculated timestamps
  • Simplified pause/resume logic
  • Updated togglePlayPause() to not track wall-clock segments

workout_storage.dart

  • Removed elapsedSeconds parameter from saveWorkoutState()
  • Removed _elapsedSecondsKey constant
  • Cleaned up state loading/saving logic

Impact

Positive Changes

✅ Workout duration consistency across app and uploads ✅ Simplified time tracking logic (removed duplicate system) ✅ No more timer drift accumulation ✅ Easier to debug and maintain

Preserved Functionality

✅ Pause/resume still works correctly ✅ Background workout continuation still works ✅ Workout state persistence still works ✅ All existing tests pass

Testing

The existing test/erg_workout_test.dart validates the fix:

  • Creates a 2-hour workout simulation
  • Generates GPX and FIT files
  • Verifies export functionality

Additional manual testing recommended:

  1. Complete a 30-minute workout without pausing

    • Verify app shows 30:00 elapsed time
    • Verify uploaded file shows 30:00 duration
  2. Complete a 30-minute workout with 2 pauses

    • Verify app shows 30:00 elapsed time
    • Verify uploaded file shows 30:00 duration
  3. Navigate away from workout screen and back during active workout

    • Verify time continues correctly
    • Verify uploaded file has correct duration

Migration Notes

No user data migration needed. Existing workout states will load correctly:

  • progressPosition and _workoutProgressTime are preserved
  • elapsedSeconds in saved state is ignored (no longer used)
  • First run after update will work seamlessly