feat: Dynamic Island pause state, Apple-aligned spacing, and UI polish

- Add isPaused to WorkoutActivityAttributes.ContentState
- Show PAUSED badge, freeze timer to static text, dim content when paused
- Prevent stale spinner on pause by extending staleDate to 1 hour
- Add 6s timer warning color, progress bar, compact heavy timer
- Pulsing compact indicator during WORK phase
- Lock Screen margins aligned to Apple's 14pt HIG spec
This commit is contained in:
Millian Lamiaux
2026-05-15 23:52:01 +02:00
parent 057fbb3c9a
commit 95f34e6471
3 changed files with 233 additions and 91 deletions

View File

@@ -152,6 +152,7 @@ final class PlayerViewModel: ObservableObject {
timer?.invalidate()
stopActivitySyncTimer()
liveSession.pause()
syncActivity()
softHaptics.impactOccurred()
}
@@ -380,7 +381,8 @@ final class PlayerViewModel: ObservableObject {
heartRate: heartRate,
trackTitle: currentTrackTitle,
trackArtist: currentTrackArtist,
isPlaying: isPlayingMusic
isPlaying: isPlayingMusic,
isPaused: isPaused
)
// Discard stale reference user may have dismissed the Dynamic Island
@@ -390,8 +392,9 @@ final class PlayerViewModel: ObservableObject {
if let existing = workoutActivity {
nonisolated(unsafe) let safeExisting = existing
let staleDate = Date().addingTimeInterval(isPaused ? 3600 : 120)
Task { @MainActor in
await safeExisting.update(ActivityContent(state: state, staleDate: Date().addingTimeInterval(120)))
await safeExisting.update(ActivityContent(state: state, staleDate: staleDate))
}
} else {
createOrUpdateActivity(with: state)
@@ -401,9 +404,10 @@ final class PlayerViewModel: ObservableObject {
private func createOrUpdateActivity(with state: WorkoutActivityAttributes.ContentState) {
let attrs = WorkoutActivityAttributes()
do {
let staleDate = Date().addingTimeInterval(isPaused ? 3600 : 120)
workoutActivity = try Activity.request(
attributes: attrs,
content: ActivityContent(state: state, staleDate: Date().addingTimeInterval(120))
content: ActivityContent(state: state, staleDate: staleDate)
)
observeActivityState()
} catch {
@@ -427,7 +431,8 @@ final class PlayerViewModel: ObservableObject {
heartRate: safeActivity.content.state.heartRate,
trackTitle: safeActivity.content.state.trackTitle,
trackArtist: safeActivity.content.state.trackArtist,
isPlaying: false
isPlaying: false,
isPaused: false
)
await safeActivity.end(ActivityContent(state: finalState, staleDate: nil), dismissalPolicy: .immediate)
}