Keep the iPhone screen awake for the whole app session

Move the idle-timer disable out of ExerciseView/ExerciseProgressView and
up to the app scene, re-asserting it whenever the scene becomes active
(iOS clears the flag on background). A propped-up phone now stays lit for
the entire workout, not just while an exercise is open.

Claude-Session: https://claude.ai/code/session_01SCv7zvGFcKy47KSTnTLxRe
This commit is contained in:
2026-06-20 21:48:03 -04:00
parent a16e8ec270
commit b911818587
4 changed files with 9 additions and 10 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ Tapping an exercise on iPhone now opens a paged workout run — the same Ready
The exercise detail and edit screen moved behind an Edit swipe on the trailing edge; the leading swipe still completes a set. The exercise detail and edit screen moved behind an Edit swipe on the trailing edge; the leading swipe still completes a set.
The iPhone screen now stays awake while an exercise is open, so it no longer sleeps mid-set. The iPhone screen now stays awake the whole time the app is open, so a propped-up phone never sleeps during a workout.
The Apple Watch is now a focused workout runner: it opens on the active workout's exercises (or prompts you to start one on iPhone) and lists every in-progress workout at the root. The Apple Watch is now a focused workout runner: it opens on the active workout's exercises (or prompts you to start one on iPhone) and lists every in-progress workout at the root.
@@ -181,8 +181,6 @@ struct ExerciseProgressView: View {
} }
} }
.onAppear { .onAppear {
// Keep the screen lit while logging a mid-workout sleep is annoying.
UIApplication.shared.isIdleTimerDisabled = true
guard !didRestorePage else { return } guard !didRestorePage else { return }
if startsResumed { if startsResumed {
// Resume on the first unfinished set. A paged TabView can settle on page 0 // Resume on the first unfinished set. A paged TabView can settle on page 0
@@ -199,9 +197,6 @@ struct ExerciseProgressView: View {
didRestorePage = true didRestorePage = true
} }
} }
.onDisappear {
UIApplication.shared.isIdleTimerDisabled = false
}
} }
/// Move to the resume page without animation, only if we're not already there /// Move to the resume page without animation, only if we're not already there
@@ -10,7 +10,6 @@
import SwiftUI import SwiftUI
import SwiftData import SwiftData
import Charts import Charts
import UIKit
struct ExerciseView: View { struct ExerciseView: View {
@Environment(SyncEngine.self) private var sync @Environment(SyncEngine.self) private var sync
@@ -68,13 +67,10 @@ struct ExerciseView: View {
.onAppear { .onAppear {
refreshDocIfNeeded() refreshDocIfNeeded()
progress = log?.currentStateIndex ?? 0 progress = log?.currentStateIndex ?? 0
// Keep the screen lit while logging sets a mid-workout sleep is annoying.
UIApplication.shared.isIdleTimerDisabled = true
// Take over this run: the watch parks and locks it while we're editing here. // Take over this run: the watch parks and locks it while we're editing here.
services.watchBridge.setEditingWorkout(workout.id) services.watchBridge.setEditingWorkout(workout.id)
} }
.onDisappear { .onDisappear {
UIApplication.shared.isIdleTimerDisabled = false
services.watchBridge.setEditingWorkout(nil) services.watchBridge.setEditingWorkout(nil)
} }
// Reflect external changes (e.g. a set completed on the watch) live. Each edit // Reflect external changes (e.g. a set completed on the watch) live. Each edit
+8
View File
@@ -7,10 +7,12 @@
import SwiftUI import SwiftUI
import SwiftData import SwiftData
import UIKit
@main @main
struct WorkoutsApp: App { struct WorkoutsApp: App {
@State private var services = AppServices() @State private var services = AppServices()
@Environment(\.scenePhase) private var scenePhase
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
@@ -24,6 +26,12 @@ struct WorkoutsApp: App {
root root
#endif #endif
} }
// Keep the screen lit for the whole app, not just while logging the phone is
// often propped up across the room during a workout. iOS clears this flag when
// the app is backgrounded, so re-assert it each time the scene becomes active.
.onChange(of: scenePhase, initial: true) { _, phase in
UIApplication.shared.isIdleTimerDisabled = (phase == .active)
}
} }
private var root: some View { private var root: some View {