An exercise's name is now a per-routine display name: a new optional libraryName on ExerciseDocument (snapshotted onto WorkoutLogDocument at plan time) keeps the link to the bundled library exercise, and every figure/info/cue lookup resolves libraryName ?? name. Deliberately not schema-bumped — an older app dropping the key only strands the figure link, same rationale as activityType. Cache schema bumped to 9 for the new columns. The picker no longer filters out exercises already in the routine (an "×N" badge marks them instead), exercise rows gain a leading Duplicate swipe that clones an entry in place, and the edit sheet gets a Name field with the library exercise shown read-only above it. Claude-Session: https://claude.ai/code/session_01H8VxUX4ckjU3vRF5M4L5FV
93 lines
3.5 KiB
Swift
93 lines
3.5 KiB
Swift
//
|
||
// ExercisePickerView.swift
|
||
// Workouts
|
||
//
|
||
// Created by rzen on 7/13/25 at 7:17 PM.
|
||
//
|
||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
struct ExercisePickerView: View {
|
||
@Environment(\.dismiss) private var dismiss
|
||
@State private var selectedExercises: Set<String> = []
|
||
|
||
var onExerciseSelected: ([String]) -> Void
|
||
var allowMultiSelect: Bool = false
|
||
/// How many times each exercise already appears in the routine being edited —
|
||
/// shown as a "×N" badge so picking it again (for interval-style segments like
|
||
/// a treadmill's warmup/brisk/cooldown) doesn't read as a mistake. Keyed by
|
||
/// library name. Empty for callers with no notion of "already in this routine".
|
||
var inRoutineCounts: [String: Int] = [:]
|
||
|
||
init(onExerciseSelected: @escaping ([String]) -> Void, allowMultiSelect: Bool = false,
|
||
inRoutineCounts: [String: Int] = [:]) {
|
||
self.onExerciseSelected = onExerciseSelected
|
||
self.allowMultiSelect = allowMultiSelect
|
||
self.inRoutineCounts = inRoutineCounts
|
||
}
|
||
|
||
var body: some View {
|
||
NavigationStack {
|
||
List(ExerciseMotionLibrary.exerciseNames, id: \.self) { exerciseName in
|
||
if allowMultiSelect {
|
||
Button(action: {
|
||
if selectedExercises.contains(exerciseName) {
|
||
selectedExercises.remove(exerciseName)
|
||
} else {
|
||
selectedExercises.insert(exerciseName)
|
||
}
|
||
}) {
|
||
HStack {
|
||
Text(exerciseName)
|
||
Spacer()
|
||
if let count = inRoutineCounts[exerciseName], count > 0 {
|
||
Text("×\(count)")
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
if selectedExercises.contains(exerciseName) {
|
||
Image(systemName: "checkmark")
|
||
.foregroundColor(.accentColor)
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
Button(action: {
|
||
onExerciseSelected([exerciseName])
|
||
dismiss()
|
||
}) {
|
||
HStack {
|
||
Text(exerciseName)
|
||
Spacer()
|
||
if let count = inRoutineCounts[exerciseName], count > 0 {
|
||
Text("×\(count)")
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.navigationTitle("Exercise Library")
|
||
.toolbar {
|
||
ToolbarItem(placement: .navigationBarLeading) {
|
||
Button("Cancel") {
|
||
dismiss()
|
||
}
|
||
}
|
||
if allowMultiSelect {
|
||
ToolbarItem(placement: .navigationBarTrailing) {
|
||
Button("Select") {
|
||
if !selectedExercises.isEmpty {
|
||
onExerciseSelected(Array(selectedExercises))
|
||
dismiss()
|
||
}
|
||
}
|
||
.disabled(selectedExercises.isEmpty)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|