Let routines rename exercises and repeat them for interval segments
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
This commit is contained in:
@@ -88,6 +88,14 @@ struct RoutineDetailView: View {
|
||||
title: item.name,
|
||||
subtitle: item.planSummary(weightUnit: weightUnit)
|
||||
)
|
||||
.swipeActions(edge: .leading) {
|
||||
Button {
|
||||
duplicateExercise(item)
|
||||
} label: {
|
||||
Label("Duplicate", systemImage: "plus.square.on.square")
|
||||
}
|
||||
.tint(.teal)
|
||||
}
|
||||
.swipeActions {
|
||||
Button {
|
||||
itemToDelete = item
|
||||
@@ -128,7 +136,11 @@ struct RoutineDetailView: View {
|
||||
.sheet(isPresented: $showingExerciseAddSheet) {
|
||||
ExercisePickerView(onExerciseSelected: { exerciseNames in
|
||||
addExercises(names: exerciseNames)
|
||||
}, allowMultiSelect: true)
|
||||
}, allowMultiSelect: true,
|
||||
inRoutineCounts: Dictionary(
|
||||
routine.exercisesArray.map { ($0.libraryExerciseName, 1) },
|
||||
uniquingKeysWith: +
|
||||
))
|
||||
}
|
||||
.sheet(isPresented: $showingRoutineEditSheet) {
|
||||
RoutineAddEditView(routine: routine) {
|
||||
@@ -179,10 +191,8 @@ struct RoutineDetailView: View {
|
||||
private func addExercises(names: [String]) {
|
||||
guard let routine else { return }
|
||||
var doc = RoutineDocument(from: routine)
|
||||
let existingNames = Set(doc.exercises.map { $0.name })
|
||||
let base = doc.exercises.count
|
||||
let newDocs = names
|
||||
.filter { !existingNames.contains($0) }
|
||||
.enumerated()
|
||||
.map { i, exName -> ExerciseDocument in
|
||||
// Seed from the library's authored `**Defaults:**` when available,
|
||||
@@ -215,4 +225,22 @@ struct RoutineDetailView: View {
|
||||
doc.updatedAt = Date()
|
||||
Task { await sync.save(routine: doc) }
|
||||
}
|
||||
|
||||
/// Copy an exercise in place, right after itself — the interval-routine case
|
||||
/// (a treadmill's "Warmup 5 min" / "Brisk Walk 10 min" / … segments all derive
|
||||
/// from the same library exercise). The clone starts out an exact duplicate,
|
||||
/// plan and any customized name included; renaming it is a follow-up edit.
|
||||
private func duplicateExercise(_ exercise: Exercise) {
|
||||
guard let routine else { return }
|
||||
var doc = RoutineDocument(from: routine)
|
||||
guard let idx = doc.exercises.firstIndex(where: { $0.id == exercise.id }) else { return }
|
||||
var copy = doc.exercises[idx]
|
||||
copy.id = ULID.make()
|
||||
doc.exercises.insert(copy, at: idx + 1)
|
||||
for i in doc.exercises.indices {
|
||||
doc.exercises[i].order = i
|
||||
}
|
||||
doc.updatedAt = Date()
|
||||
Task { await sync.save(routine: doc) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user