Polish the New Workout form: segmented When on top, pushed routine list

- When section moves to the top as a segmented control (3 or 4 segments
  depending on whether Now is offered)
- Routine selection is a pushed list with each routine's symbol and color,
  checkmark on the current pick, select-and-pop
- Goal "None" reserves the symbol slot so all goal labels align
This commit is contained in:
2026-07-11 11:39:29 -04:00
parent 7cb2d6da26
commit df07e0a043
+66 -9
View File
@@ -111,6 +111,13 @@ struct ScheduleAddEditView: View {
/// The live routine currently selected; nil when the stored id no longer resolves.
private var selectedRoutine: Routine? { routines.first { $0.id == routineID } }
/// The When choices this presentation offers "Now" only when adding on today.
/// Built as a list (rather than conditionally hiding a tag inside the Picker)
/// so the segmented control renders a clean 3- or 4-segment strip.
private var availableChoices: [WhenChoice] {
offersNow ? WhenChoice.allCases : WhenChoice.allCases.filter { $0 != .now }
}
private var canSave: Bool {
guard selectedRoutine != nil else { return false }
if when == .fixedDays { return !weekdays.isEmpty }
@@ -120,11 +127,11 @@ struct ScheduleAddEditView: View {
var body: some View {
NavigationStack {
Form {
whenSection
routineSection
if when != .now {
goalSection
}
whenSection
}
.navigationTitle(isEditing ? "Edit Workout" : "New Workout")
.navigationBarTitleDisplayMode(.inline)
@@ -148,9 +155,14 @@ struct ScheduleAddEditView: View {
private var routineSection: some View {
Section {
Picker("Routine", selection: $routineID) {
ForEach(routines) { routine in
Text(routine.name).tag(routine.id)
NavigationLink {
RoutinePickerList(routines: routines, selectedID: $routineID)
} label: {
HStack {
Text("Routine")
Spacer()
Text(selectedRoutine?.name ?? "Choose…")
.foregroundStyle(.secondary)
}
}
} header: {
@@ -165,7 +177,14 @@ struct ScheduleAddEditView: View {
private var goalSection: some View {
Section {
Picker("Goal", selection: $goal) {
Text("None").tag(GoalKind?.none)
// A hidden symbol placeholder keeps "None" aligned with the
// symbol-bearing goal rows below it.
Label {
Text("None")
} icon: {
Image(systemName: "tag").hidden()
}
.tag(GoalKind?.none)
ForEach(GoalKind.orderedCases()) { kind in
Label(kind.displayName, systemImage: kind.systemImage)
.foregroundStyle(Color.color(from: kind.colorName))
@@ -183,12 +202,11 @@ struct ScheduleAddEditView: View {
private var whenSection: some View {
Section {
Picker("When", selection: $when) {
ForEach(WhenChoice.allCases) { choice in
if choice != .now || offersNow {
Text(choice.displayName).tag(choice)
}
ForEach(availableChoices) { choice in
Text(choice.displayName).tag(choice)
}
}
.pickerStyle(.segmented)
switch when {
case .now, .daily:
@@ -260,3 +278,42 @@ struct ScheduleAddEditView: View {
return (existing.map(\.order).max() ?? -1) + 1
}
}
// MARK: - Routine picker
/// The pushed routine-selection list: each routine with its own symbol and color
/// (the Today board's row treatment), a checkmark on the current selection.
/// Tapping selects and pops back.
private struct RoutinePickerList: View {
let routines: [Routine]
@Binding var selectedID: String
@Environment(\.dismiss) private var dismiss
var body: some View {
List(routines) { routine in
Button {
selectedID = routine.id
dismiss()
} label: {
HStack(spacing: 12) {
Image(systemName: routine.systemImage)
.font(.title3)
.foregroundStyle(Color.color(from: routine.color))
.frame(width: 32)
Text(routine.name)
.foregroundStyle(.primary)
Spacer()
if routine.id == selectedID {
Image(systemName: "checkmark")
.fontWeight(.semibold)
.foregroundStyle(Color.accentColor)
}
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
.navigationTitle("Routine")
.navigationBarTitleDisplayMode(.inline)
}
}