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:
@@ -1,52 +0,0 @@
|
||||
//
|
||||
// CheckboxListItem.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/13/25 at 10:42 AM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CheckboxListItem: View {
|
||||
var status: CheckboxStatus
|
||||
var title: String
|
||||
var subtitle: String?
|
||||
var count: Int?
|
||||
var onCheckboxTap: (() -> Void)? = nil
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
Button {
|
||||
onCheckboxTap?()
|
||||
} label: {
|
||||
Image(systemName: status.systemName)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 30, height: 30)
|
||||
.foregroundStyle(status.color)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
VStack(alignment: .leading) {
|
||||
Text("\(title)")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
HStack(alignment: .bottom) {
|
||||
if let subtitle = subtitle {
|
||||
Text("\(subtitle)")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
if let count = count {
|
||||
Text("\(count)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,8 @@ enum CheckboxStatus {
|
||||
switch self {
|
||||
case .checked: .accentColor
|
||||
case .unchecked: .gray
|
||||
case .intermediate: .gray
|
||||
case .cancelled: .red
|
||||
case .intermediate: .accentColor
|
||||
case .cancelled: .gray
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ enum CheckboxStatus {
|
||||
case .checked: "checkmark.circle.fill"
|
||||
case .unchecked: "circle"
|
||||
case .intermediate: "ellipsis.circle"
|
||||
case .cancelled: "xmark.circle"
|
||||
case .cancelled: "wrongwaysign"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// 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) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user