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
90 lines
2.9 KiB
Swift
90 lines
2.9 KiB
Swift
//
|
|
// SplitListView.swift
|
|
// Workouts
|
|
//
|
|
// Created by rzen on 7/25/25 at 6:24 PM.
|
|
//
|
|
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
|
//
|
|
|
|
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
|
|
|
|
@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) {
|
|
ForEach(splits) { split in
|
|
NavigationLink {
|
|
SplitDetailView(split: split)
|
|
} label: {
|
|
SplitItem(split: split)
|
|
}
|
|
.contextMenu {
|
|
Button(role: .destructive) {
|
|
splitToDelete = split
|
|
} label: {
|
|
Label("Delete", systemImage: "trash")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
.overlay {
|
|
if splits.isEmpty {
|
|
ContentUnavailableView(
|
|
"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.")
|
|
}
|
|
}
|
|
}
|