Files
workouts/Workouts/Views/Common/MachineSettingsEditor.swift
T
rzen 2c1e4759ae 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
2026-07-06 16:29:44 -04:00

71 lines
2.5 KiB
Swift

//
// MachineSettingsEditor.swift
// Workouts
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
/// Reusable editor for an ordered list of machine comfort settings (seat height,
/// back-rest incline, pin position…). Drop it into a `Form`/`List` `Section`: it
/// renders one `name`/`value` row per setting with swipe-to-delete and
/// drag-to-reorder, plus an "Add Setting" row. Shared by the exercise add/edit
/// screen (Surface 1) and the in-workout settings sheet (Surface 2).
///
/// `MachineSetting` is a plain Codable wire type with no identity, so the editor
/// keeps a per-row `UUID` internally (stable across edits/reorders, unlike an
/// index) and mirrors changes back into the `settings` binding. The editor is the
/// sole mutator of `settings` while it's on screen, so a one-way seed at init is
/// enough — there's no external write to reconcile.
struct MachineSettingsEditor: View {
@Binding var settings: [MachineSetting]
@State private var rows: [Row]
init(settings: Binding<[MachineSetting]>) {
_settings = settings
_rows = State(initialValue: settings.wrappedValue.map(Row.init))
}
var body: some View {
ForEach($rows) { $row in
HStack {
TextField("Setting", text: $row.name)
.textInputAutocapitalization(.words)
TextField("Value", text: $row.value)
.multilineTextAlignment(.trailing)
.foregroundStyle(.secondary)
.frame(maxWidth: 120)
}
}
.onDelete { rows.remove(atOffsets: $0) }
.onMove { rows.move(fromOffsets: $0, toOffset: $1) }
Button {
withAnimation { rows.append(Row(MachineSetting(name: "", value: ""))) }
} label: {
Label("Add Setting", systemImage: "plus.circle.fill")
}
// Mirror the identified rows back into the plain wire-type binding on every
// edit/add/delete/reorder.
.onChange(of: rows) { _, newRows in
settings = newRows.map(\.setting)
}
}
/// Session-only identified wrapper so `ForEach` and reordering have a stable
/// identity that survives text edits.
fileprivate struct Row: Identifiable, Equatable {
let id = UUID()
var name: String
var value: String
init(_ setting: MachineSetting) {
name = setting.name
value = setting.value
}
var setting: MachineSetting { MachineSetting(name: name, value: value) }
}
}