feat: redesign player with Dynamic Island, compact timer, and fix Live Activity timer drift #2

Merged
millianlmx merged 18 commits from revamp-timer-video-layout into main 2026-05-23 12:24:34 +02:00
Showing only changes of commit e42c1217db - Show all commits

View File

@@ -116,6 +116,8 @@ final class PlayerViewModel: ObservableObject {
}
func abandonWorkout() {
isRunning = false
isPaused = false
timer?.invalidate()
stopActivitySyncTimer()
Task { try? await liveSession.end() }
@@ -401,7 +403,8 @@ final class PlayerViewModel: ObservableObject {
phaseElapsedSeconds: max(0, Double(totalPhaseTime) - Double(timeRemaining))
)
if let existing = workoutActivity, existing.activityState != .active {
if let existing = workoutActivity,
existing.activityState == .ended || existing.activityState == .dismissed {
workoutActivity = nil
}
@@ -449,12 +452,14 @@ final class PlayerViewModel: ObservableObject {
}
func endActivity() async {
stopActivitySyncTimer()
guard let activity = workoutActivity else { return }
workoutActivity = nil
activityStateTask?.cancel()
activityStateTask = nil
nonisolated(unsafe) let safeActivity = activity
guard safeActivity.activityState == .active else { return }
guard safeActivity.activityState != .ended,
safeActivity.activityState != .dismissed else { return }
let finalState = WorkoutActivityAttributes.ContentState(
exerciseName: safeActivity.content.state.exerciseName,
phase: .complete,
@@ -497,7 +502,11 @@ final class PlayerViewModel: ObservableObject {
activityStateTask = Task { @MainActor [weak self] in
for await state in activity.activityStateUpdates {
guard let self else { return }
if state == .stale || state == .ended || state == .dismissed {
if state == .stale {
// Stop sync timer, but keep the activity reference
// so endActivity() can still call .end() to properly dismiss it.
self.stopActivitySyncTimer()
} else if state == .ended || state == .dismissed {
self.workoutActivity = nil
self.stopActivitySyncTimer()
}