Add a starter routine gallery with per-seed restore
The Library tab's routines pane gains a "Browse Starter Routines" entry opening a gallery of every bundled seed — kept or deleted — each showing Added / name-taken / restorable state, a read-only plan preview, and a one-tap Add that lifts the seed's delete veto. A "Re-add All Missing Starters" fallback replaces the all-or-nothing Settings button, which is removed. The delete confirmation now also warns how many scheduled days would be left behind on the Today board. Claude-Session: https://claude.ai/code/session_01H8VxUX4ckjU3vRF5M4L5FV
This commit is contained in:
@@ -25,6 +25,8 @@ struct RoutinesLibraryView: View {
|
||||
@Query(sort: \Workout.start, order: .reverse)
|
||||
private var workouts: [Workout]
|
||||
|
||||
@Query private var schedules: [Schedule]
|
||||
|
||||
@State private var showingAddSheet = false
|
||||
@State private var routineToEdit: Routine?
|
||||
@State private var routineToDelete: Routine?
|
||||
@@ -50,35 +52,48 @@ struct RoutinesLibraryView: View {
|
||||
var body: some View {
|
||||
let lastTrained = lastTrainedByRoutineID
|
||||
List {
|
||||
ForEach(routines) { routine in
|
||||
// A separate Section so the browse-starters row never joins the
|
||||
// reorderable ForEach below (EditButton's drag/delete affordances only
|
||||
// ever apply to actual routines).
|
||||
Section {
|
||||
ForEach(routines) { routine in
|
||||
NavigationLink {
|
||||
RoutineDetailView(routine: routine)
|
||||
} label: {
|
||||
RoutineRow(routine: routine, lastTrained: lastTrained[routine.id])
|
||||
}
|
||||
.swipeActions(edge: .leading) {
|
||||
Button {
|
||||
Task { await sync.duplicate(routine: routine) }
|
||||
} label: {
|
||||
Label("Duplicate", systemImage: "plus.square.on.square")
|
||||
}
|
||||
.tint(.teal)
|
||||
}
|
||||
.swipeActions {
|
||||
Button(role: .destructive) {
|
||||
routineToDelete = routine
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
Button {
|
||||
routineToEdit = routine
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
.tint(.indigo)
|
||||
}
|
||||
}
|
||||
.onMove(perform: moveRoutines)
|
||||
}
|
||||
|
||||
Section {
|
||||
NavigationLink {
|
||||
RoutineDetailView(routine: routine)
|
||||
StarterGalleryView()
|
||||
} label: {
|
||||
RoutineRow(routine: routine, lastTrained: lastTrained[routine.id])
|
||||
}
|
||||
.swipeActions(edge: .leading) {
|
||||
Button {
|
||||
Task { await sync.duplicate(routine: routine) }
|
||||
} label: {
|
||||
Label("Duplicate", systemImage: "plus.square.on.square")
|
||||
}
|
||||
.tint(.teal)
|
||||
}
|
||||
.swipeActions {
|
||||
Button(role: .destructive) {
|
||||
routineToDelete = routine
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
Button {
|
||||
routineToEdit = routine
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
.tint(.indigo)
|
||||
Label("Browse Starter Routines", systemImage: "sparkles")
|
||||
}
|
||||
}
|
||||
.onMove(perform: moveRoutines)
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.overlay {
|
||||
@@ -127,10 +142,21 @@ struct RoutinesLibraryView: View {
|
||||
routineToDelete = nil
|
||||
}
|
||||
} message: { routine in
|
||||
Text("This will permanently delete \"\(routine.name)\" and all its exercises. Past workouts are kept.")
|
||||
Text(deleteMessage(for: routine))
|
||||
}
|
||||
}
|
||||
|
||||
/// The delete-confirmation body, extended with a note about any schedules that
|
||||
/// reference this routine — deleting the routine leaves them behind (schedules
|
||||
/// aren't cascaded), so the Today board would otherwise show an orphaned day with
|
||||
/// no explanation.
|
||||
private func deleteMessage(for routine: Routine) -> String {
|
||||
let base = "This will permanently delete \"\(routine.name)\" and all its exercises. Past workouts are kept."
|
||||
let count = schedules.filter { sync.currentRoutineID(for: $0.routineID) == routine.id }.count
|
||||
guard count > 0 else { return base }
|
||||
return base + " Its \(count) scheduled day\(count == 1 ? "" : "s") will stay on the Today board without a startable routine."
|
||||
}
|
||||
|
||||
/// Reorder and renumber via `RoutineOrdering`, writing only the routines whose
|
||||
/// order actually changed. Order-only writes deliberately leave `updatedAt`
|
||||
/// alone — it's bookkeeping, not content, and touching it needlessly risks forking
|
||||
|
||||
Reference in New Issue
Block a user