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:
@@ -10,12 +10,19 @@
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
/// The Splits library screen, pushed from Settings → Library: a two-column grid of
|
||||
/// split tiles. Tapping a tile opens `SplitDetailView`; long-pressing offers Delete
|
||||
/// (the sole split-delete affordance); the toolbar's + adds a new split. Starter
|
||||
/// splits are seeded automatically on the true first run (`SyncEngine.autoSeedIfEmpty`),
|
||||
/// so an empty library just invites creating one.
|
||||
struct SplitListView: View {
|
||||
@Environment(SyncEngine.self) private var sync
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
|
||||
@Query(sort: \Split.order) private var splits: [Split]
|
||||
|
||||
@State private var showingAddSheet = false
|
||||
@State private var splitToDelete: Split?
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
|
||||
@@ -25,6 +32,13 @@ struct SplitListView: View {
|
||||
} label: {
|
||||
SplitItem(split: split)
|
||||
}
|
||||
.contextMenu {
|
||||
Button(role: .destructive) {
|
||||
splitToDelete = split
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
@@ -32,20 +46,44 @@ struct SplitListView: View {
|
||||
.overlay {
|
||||
if splits.isEmpty {
|
||||
ContentUnavailableView(
|
||||
label: {
|
||||
Label("No Splits Yet", systemImage: "dumbbell.fill")
|
||||
},
|
||||
description: {
|
||||
Text("Create a split to organize your workout routine.")
|
||||
},
|
||||
actions: {
|
||||
Button("Add Starter Splits") {
|
||||
Task { await SplitSeeder.seedDefaults(into: modelContext, using: sync) }
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
"No Splits Yet",
|
||||
systemImage: "dumbbell.fill",
|
||||
description: Text("Create a split to organize your workout routine.")
|
||||
)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Splits")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showingAddSheet = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
.accessibilityLabel("Add Split")
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingAddSheet) {
|
||||
SplitAddEditView(split: nil)
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Delete Split?",
|
||||
isPresented: Binding(
|
||||
get: { splitToDelete != nil },
|
||||
set: { if !$0 { splitToDelete = nil } }
|
||||
),
|
||||
titleVisibility: .visible,
|
||||
presenting: splitToDelete
|
||||
) { split in
|
||||
Button("Delete", role: .destructive) {
|
||||
Task { await sync.delete(split: split) }
|
||||
splitToDelete = nil
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
splitToDelete = nil
|
||||
}
|
||||
} message: { split in
|
||||
Text("This will permanently delete \"\(split.name)\" and all its exercises. Past workouts are kept.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user