Surface routine usage, starter-copy notices, and a grouped exercise picker
Routine detail gains a read-time Usage section (last trained, completed workout count, linked schedules) resolved through the clone redirect, and both it and the edit sheet now explain that editing a starter saves your own copy. The add-exercise picker adopts the same curated category sections and name/category/muscle search as the exercise library. Claude-Session: https://claude.ai/code/session_01H8VxUX4ckjU3vRF5M4L5FV
This commit is contained in:
@@ -12,6 +12,7 @@ import SwiftUI
|
||||
struct ExercisePickerView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var selectedExercises: Set<String> = []
|
||||
@State private var searchText = ""
|
||||
|
||||
var onExerciseSelected: ([String]) -> Void
|
||||
var allowMultiSelect: Bool = false
|
||||
@@ -28,47 +29,34 @@ struct ExercisePickerView: View {
|
||||
self.inRoutineCounts = inRoutineCounts
|
||||
}
|
||||
|
||||
/// The same curated, searchable grouping `ExerciseLibraryView` uses, so picking an
|
||||
/// exercise reads the same way as browsing the library.
|
||||
private var sections: [ExerciseCatalogSection] {
|
||||
ExerciseCatalog.sections(matching: searchText)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Group {
|
||||
if sections.isEmpty {
|
||||
ContentUnavailableView.search(text: searchText)
|
||||
} else {
|
||||
Button(action: {
|
||||
onExerciseSelected([exerciseName])
|
||||
dismiss()
|
||||
}) {
|
||||
HStack {
|
||||
Text(exerciseName)
|
||||
Spacer()
|
||||
if let count = inRoutineCounts[exerciseName], count > 0 {
|
||||
Text("×\(count)")
|
||||
.foregroundStyle(.secondary)
|
||||
List {
|
||||
ForEach(sections, id: \.title) { section in
|
||||
Section(section.title) {
|
||||
ForEach(section.names, id: \.self) { exerciseName in
|
||||
row(for: exerciseName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Exercise Library")
|
||||
.searchable(
|
||||
text: $searchText,
|
||||
placement: .navigationBarDrawer(displayMode: .always),
|
||||
prompt: "Name, category, or muscle")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
@@ -89,4 +77,44 @@ struct ExercisePickerView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func row(for exerciseName: String) -> some View {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user