Skip to content

feat(ios): Workout Live Activity with its own switch - #2895

Open
Sternpaul wants to merge 1 commit into
ryanbr:mainfrom
Sternpaul:feat/workout-live-activity
Open

Sternpaul wants to merge 1 commit into
ryanbr:mainfrom
Sternpaul:feat/workout-live-activity

Conversation

@Sternpaul

Copy link
Copy Markdown

What this PR does

Adds a Live Activity for a live-tracked workout (a run, ride, walk, or any sport started from the workout picker) on the Lock Screen and in the Dynamic Island.

  • Lock Screen: sport, live heart rate, duration, GPS distance and average pace. A workout with no route shows duration and heart rate only.
  • Dynamic Island: sport icon and clock (compact leading), pace or HR (compact trailing), all three figures (expanded).

It is its own activity type (WorkoutActivityAttributes) with its own switch, Settings › Live notifications › Workout (on by default), so it works with the Live HR banner turned off. While it shows, the Live HR banner steps aside and a sync starts no new banner, the same as for a Lift Log session.

Details:

  • The clock is Text(.timer) from the start time shifted by paused time, so iOS counts it up by itself between pushes. While paused it shows the frozen active time.
  • Labels are localized and distance/pace formatted in the user's units on the app side, because the widget extension has no string catalog or unit prefs. The one new Settings string has translations for every catalog locale.
  • Pushes: pause/resume immediately; changed HR, distance or pace at most every 5 s.
  • AppModel.activeWorkoutIsGps becomes private(set) so the banner uses the same GPS check as the live workout screen's distance row.

iOS only: Live Activities have no Android counterpart. An ongoing-notification equivalent could follow separately.

Overlap with #2761

#2761 also adds a workout Live Activity (Start/Pause/Resume/End controls and a clock, part of training favorites). It does not show distance, pace or heart rate, which is the focus here. Both PRs use the names WorkoutActivityAttributes / WorkoutLiveActivity, so whichever lands second needs adapting. I'm happy to rebase on top of #2761 and move distance, pace and heart rate into its TrainingDisplay, or to keep this PR standalone, whichever the maintainers prefer.

Type of change

  • New feature

How it was tested

  • Built with the fork's "Testing build (fork)" workflow and sideloaded on an iPhone with a WHOOP 5.0.
  • Started a Running workout and locked the phone: the banner showed sport, live HR, a ticking duration, distance and pace (screenshots below).
  • Paused and resumed mid-workout: the banner showed "Paused" with the clock frozen, then resumed counting from the right active time.
  • Tools/i18n_audit.py --ci and Tools/doc_comment_lint.py report nothing new.

Screenshots

Lock Screen while running:

Lock Screen Live Activity during a run: Running, heart rate 88, duration 1:06, distance 7 m, pace 37:06 per km

Paused:

Lock Screen Live Activity while paused: Running with a Paused tag, heart rate 81, duration frozen at 0:15, distance 0 m, pace shown as a dash

Dynamic Island (compact), before the first pace:

Compact Dynamic Island during a workout: running icon and clock 0:12 on the left, heart rate 84 on the right

Checklist

  • UI changes use only StrandDesign tokens
  • No hardcoded hex frame bytes; no BLE changes
  • Follows the conventions in docs/CONTRIBUTING.md
  • I did not commit generated output (Strand.xcodeproj/) or any secrets/keystores

Related issues

Refs #757
Refs #1195

A separate Live Activity for a live-tracked workout, on the Lock Screen
and in the Dynamic Island:

- Lock Screen: sport, live heart rate, duration, GPS distance and
  average pace. A workout with no route shows duration + heart rate.
- Dynamic Island: sport glyph + clock (compact leading), pace or HR
  (compact trailing), the three figures (expanded).

It is its own activity type (WorkoutActivityAttributes) with its own
switch, Settings > Live notifications > Workout (default on), so it
works with the live-HR banner turned off. While it shows, the live-HR
banner stands aside for it and a sync starts no new banner, the same as
for a Lift Log session.

The clock is Text(.timer) from the start shifted by paused time, so iOS
counts it up between pushes; paused, it shows the frozen active time.
Labels are localized and distance/pace unit-formatted app-side, since
the widget extension has no string catalog or unit prefs. Pushes:
pause/resume at once, changed HR/distance/pace at most every 5 s.

AppModel.activeWorkoutIsGps becomes private(set) so the controller can
apply the same GPS gate as the live workout screen's distance row.

Not yet compiled: written on Windows, needs app-build.yml (iOS leg) or
a local xcodebuild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant