import SwiftUI import SwiftData /// Every bundled starter routine — including deleted ones — with a per-seed Add/Restore /// action and a bulk "re-add everything missing" fallback. Reads straight from /// `SeedLibrary.seeds` (the bundle source of truth), so a deleted starter still has a /// row. Presented as a sheet from the sidebar; selecting a seed pushes its full plan. /// Mirrors iOS `StarterGalleryView`. struct MacStarterGalleryView: View { @Environment(SyncEngine.self) private var syncEngine @Environment(\.dismiss) private var dismiss @Query(sort: [SortDescriptor(\Routine.order), SortDescriptor(\Routine.name)]) private var routines: [Routine] @State private var isRestoringAll = false @State private var restoreAllMessage: String? var body: some View { NavigationStack { List { Section { ForEach(SeedLibrary.seeds, id: \.id) { seed in NavigationLink(value: seed.id) { row(for: seed) } } } footer: { Text("Every routine Workouts ships with, whether or not you've kept it. Adding one never touches a routine you've created or edited.") .font(.caption) .foregroundStyle(.secondary) } if let restoreAllMessage { Section { Text(restoreAllMessage) .font(.caption) .foregroundStyle(.secondary) } } } .navigationTitle("Starter Routines") .navigationDestination(for: String.self) { seedID in if let seed = SeedLibrary.seed(id: seedID) { MacStarterPreview(seed: seed) } } .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Done") { dismiss() } } ToolbarItem { Button { Task { isRestoringAll = true restoreAllMessage = nil let n = await syncEngine.restoreSeeds() isRestoringAll = false restoreAllMessage = n == 0 ? "All starter routines are already present." : "Restored \(n) starter routine\(n == 1 ? "" : "s")." } } label: { Label("Re-add All Missing", systemImage: "arrow.counterclockwise") } .disabled(isRestoringAll || syncEngine.iCloudStatus != .available) .help("Re-add every starter routine you've removed") } } } .frame(width: 520, height: 560) } @ViewBuilder private func row(for seed: SeedLibrary.Seed) -> some View { HStack { content(for: seed.doc) Spacer() switch seed.state(among: routines) { case .added: Image(systemName: "checkmark.circle.fill").foregroundStyle(.green) case .nameTaken: EmptyView() case .restorable: Button("Add") { Task { _ = await syncEngine.restoreSeed(id: seed.id) } } .buttonStyle(.borderless) .disabled(syncEngine.iCloudStatus != .available) } } } private func content(for doc: RoutineDocument) -> some View { HStack(spacing: 10) { Image(systemName: doc.systemImage) .font(.body) .foregroundStyle(Color.color(from: doc.color)) .frame(width: 28, height: 28) .background(Color.color(from: doc.color).opacity(0.12), in: RoundedRectangle(cornerRadius: 6)) VStack(alignment: .leading, spacing: 1) { Text(doc.name) Text("\(doc.exercises.count) exercises") .font(.caption) .foregroundStyle(.secondary) } } .padding(.vertical, 2) } } /// Read-only preview of one bundled starter — icon, activity type, full exercise plan — /// with the state-aware install action. Mirrors iOS `StarterPreviewView`. private struct MacStarterPreview: View { @Environment(SyncEngine.self) private var syncEngine @Environment(\.dismiss) private var dismiss @Query(sort: [SortDescriptor(\Routine.order), SortDescriptor(\Routine.name)]) private var routines: [Routine] @AppStorage("weightUnit") private var weightUnit: WeightUnit = .lb let seed: SeedLibrary.Seed @State private var isAdding = false private var state: StarterSeedState { seed.state(among: routines) } private var activityType: WorkoutActivityType { seed.doc.activityType.flatMap(WorkoutActivityType.init(rawValue:)) ?? .traditionalStrength } var body: some View { ScrollView { VStack(alignment: .leading, spacing: 20) { header Divider() exerciseList } .padding() .frame(maxWidth: .infinity, alignment: .leading) } .safeAreaInset(edge: .bottom) { actionBar } .navigationTitle(seed.doc.name) } private var header: some View { HStack(spacing: 14) { Image(systemName: seed.doc.systemImage) .font(.largeTitle) .foregroundStyle(Color.color(from: seed.doc.color)) .frame(width: 56, height: 56) .background(Color.color(from: seed.doc.color).opacity(0.12), in: RoundedRectangle(cornerRadius: 14)) VStack(alignment: .leading, spacing: 4) { Text(seed.doc.name) .font(.title2.weight(.semibold)) Label(activityType.displayName, systemImage: activityType.systemImage) .font(.subheadline) .foregroundStyle(.secondary) } } } private var exerciseList: some View { VStack(alignment: .leading, spacing: 14) { Text("Exercises").font(.headline) ForEach(seed.doc.exercises.sorted { $0.order < $1.order }) { exercise in VStack(alignment: .leading, spacing: 2) { Text(exercise.name) Text(exercise.planSummary(weightUnit: weightUnit)) .font(.caption) .foregroundStyle(.secondary) } } } } @ViewBuilder private var actionBar: some View { Group { switch state { case .added: Label("Added to My Routines", systemImage: "checkmark.circle.fill") .foregroundStyle(.green) .frame(maxWidth: .infinity) case .nameTaken: Text("A routine named \"\(seed.doc.name)\" already exists.") .font(.callout) .foregroundStyle(.secondary) .frame(maxWidth: .infinity) case .restorable: Button { Task { isAdding = true let added = await syncEngine.restoreSeed(id: seed.id) isAdding = false if added { dismiss() } } } label: { HStack { if isAdding { ProgressView().controlSize(.small) } Text(isAdding ? "Adding…" : "Add to My Routines") } .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .disabled(isAdding || syncEngine.iCloudStatus != .available) } } .padding() .background(.thinMaterial) } }