Add machine comfort settings and tighten the document schemas
Machine-based exercises now carry ordered name/value comfort settings (seat height, back-rest position, ...) on both ExerciseDocument and, as a plan-time snapshot, WorkoutLogDocument — the optional array doubles as the machine flag. Editable from the exercise editor's new Machine section and from the workout row's settings sheet, whose mid-workout edits write back to the originating split (workout saved first, so seed clone-on-edit repointing can't clobber the log edit). Schema tightening rides the same rev: splits bump to v2 (weight-reminder fields and the unused exercise category removed), workouts to v3 (the derived `completed` flag removed; status is the single source). Starter seeds regenerated at v2 with unchanged ULIDs; SwiftData cache schema bumped to rebuild. SCHEMA.md documents the shapes. Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
This commit is contained in:
@@ -189,25 +189,38 @@ struct ExerciseProgressView: View {
|
||||
|
||||
var body: some View {
|
||||
if startsCompleted {
|
||||
CompletedPhaseView()
|
||||
// Same top/bottom split as the run flow, so the form-guide figure
|
||||
// stays on screen (matching the iPhone counterpart).
|
||||
VStack(spacing: 0) {
|
||||
CompletedPhaseView()
|
||||
ExerciseFigureSlot(exerciseName: log?.exerciseName ?? "")
|
||||
}
|
||||
} else {
|
||||
flowBody
|
||||
}
|
||||
}
|
||||
|
||||
private var flowBody: some View {
|
||||
TabView(selection: $currentPage) {
|
||||
ForEach(0..<totalPages, id: \.self) { index in
|
||||
page(for: index)
|
||||
.tag(index)
|
||||
VStack(spacing: 0) {
|
||||
// Paged flow — top half.
|
||||
TabView(selection: $currentPage) {
|
||||
ForEach(0..<totalPages, id: \.self) { index in
|
||||
page(for: index)
|
||||
.tag(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
.overlay(alignment: .bottom) {
|
||||
if let dots = workDots {
|
||||
WorkPhaseDots(model: dots)
|
||||
.padding(.bottom, 2)
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.overlay(alignment: .bottom) {
|
||||
if let dots = workDots {
|
||||
WorkPhaseDots(model: dots)
|
||||
.padding(.bottom, 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom half: the looping form-guide figure when a bundled motion
|
||||
// matches this exercise; empty space otherwise.
|
||||
ExerciseFigureSlot(exerciseName: log?.exerciseName ?? "")
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
@@ -464,7 +477,7 @@ struct ExerciseProgressView: View {
|
||||
private func beginExercise() {
|
||||
guard let i = doc.logs.firstIndex(where: { $0.id == logID }) else { return }
|
||||
guard doc.logs[i].status == WorkoutStatus.notStarted.rawValue else { return }
|
||||
doc.logs[i].status = WorkoutStatus.inProgress.rawValue
|
||||
doc.logs[i].transition(to: .inProgress)
|
||||
recomputeWorkoutStatus()
|
||||
doc.updatedAt = Date()
|
||||
onChange()
|
||||
@@ -478,8 +491,7 @@ struct ExerciseProgressView: View {
|
||||
if let i = doc.logs.firstIndex(where: { $0.id == logID }) {
|
||||
doc.logs[i].sets = newCount
|
||||
doc.logs[i].currentStateIndex = newCount - 1 // every prior set is now complete
|
||||
doc.logs[i].status = WorkoutStatus.inProgress.rawValue
|
||||
doc.logs[i].completed = false
|
||||
doc.logs[i].transition(to: .inProgress)
|
||||
recomputeWorkoutStatus()
|
||||
doc.updatedAt = Date()
|
||||
onChange()
|
||||
@@ -506,8 +518,7 @@ struct ExerciseProgressView: View {
|
||||
guard reached > doc.logs[i].currentStateIndex else { return }
|
||||
|
||||
doc.logs[i].currentStateIndex = reached
|
||||
doc.logs[i].status = WorkoutStatus.inProgress.rawValue
|
||||
doc.logs[i].completed = false
|
||||
doc.logs[i].transition(to: .inProgress)
|
||||
|
||||
recomputeWorkoutStatus()
|
||||
doc.updatedAt = Date()
|
||||
@@ -520,11 +531,9 @@ struct ExerciseProgressView: View {
|
||||
let log = doc.logs[i]
|
||||
// Skip the write if it's already pristine (e.g. landing on Ready before any set).
|
||||
guard log.currentStateIndex != 0
|
||||
|| log.status != WorkoutStatus.notStarted.rawValue
|
||||
|| log.completed else { return }
|
||||
|| log.status != WorkoutStatus.notStarted.rawValue else { return }
|
||||
doc.logs[i].currentStateIndex = 0
|
||||
doc.logs[i].status = WorkoutStatus.notStarted.rawValue
|
||||
doc.logs[i].completed = false
|
||||
doc.logs[i].transition(to: .notStarted)
|
||||
recomputeWorkoutStatus()
|
||||
doc.updatedAt = Date()
|
||||
onChange()
|
||||
@@ -533,8 +542,7 @@ struct ExerciseProgressView: View {
|
||||
private func completeExercise() {
|
||||
guard let i = doc.logs.firstIndex(where: { $0.id == logID }) else { return }
|
||||
doc.logs[i].currentStateIndex = setCount
|
||||
doc.logs[i].status = WorkoutStatus.completed.rawValue
|
||||
doc.logs[i].completed = true
|
||||
doc.logs[i].transition(to: .completed)
|
||||
|
||||
recomputeWorkoutStatus()
|
||||
doc.updatedAt = Date()
|
||||
|
||||
Reference in New Issue
Block a user