// // StarterGalleryView.swift // Workouts // // Copyright 2026 Rouslan Zenetl. All Rights Reserved. // import SwiftUI import SwiftData /// Every bundled starter routine — including ones the user has deleted — with a /// per-seed Add/Restore action and a bulk "re-add everything missing" fallback. Unlike /// `RoutinesLibraryView` (which only ever shows *live* routines), this reads straight /// from `SeedLibrary.seeds`, the bundle-derived source of truth, so a deleted starter /// still has a row here. Pushed from that view's "Browse Starter Routines" entry. struct StarterGalleryView: View { @Environment(SyncEngine.self) private var sync @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 { List { Section { ForEach(SeedLibrary.seeds, id: \.id) { seed in 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.") } Section { Button { Task { isRestoringAll = true restoreAllMessage = nil let n = await sync.restoreSeeds() isRestoringAll = false restoreAllMessage = n == 0 ? "All starter routines are already present." : "Restored \(n) starter routine\(n == 1 ? "" : "s")." } } label: { HStack { Label("Re-add All Missing Starters", systemImage: "arrow.counterclockwise") if isRestoringAll { Spacer() ProgressView() } } } .disabled(isRestoringAll || sync.iCloudStatus != .available) } footer: { if let restoreAllMessage { Text(restoreAllMessage) } } } .listStyle(.insetGrouped) .navigationTitle("Starter Routines") .navigationBarTitleDisplayMode(.inline) } // MARK: - Row /// One seed's row: always navigable (to the live detail once added, otherwise the /// read-only preview), with a state-appropriate trailing accessory — a checkmark /// once added, an inline Add button while restorable (a `.borderless` button style /// keeps its tap from also firing the row's `NavigationLink`), or nothing extra /// when blocked (the preview screen explains why). @ViewBuilder private func row(for seed: SeedLibrary.Seed) -> some View { switch seed.state(among: routines) { case .added(let routine): NavigationLink { RoutineDetailView(routine: routine) } label: { HStack { content(for: seed.doc) Spacer() Image(systemName: "checkmark.circle.fill") .foregroundStyle(.green) } } case .nameTaken: NavigationLink { StarterPreviewView(seed: seed) } label: { content(for: seed.doc, caption: "A routine named \"\(seed.doc.name)\" already exists") } case .restorable: NavigationLink { StarterPreviewView(seed: seed) } label: { HStack { content(for: seed.doc) Spacer() Button("Add") { Task { _ = await sync.restoreSeed(id: seed.id) } } .buttonStyle(.borderless) .disabled(sync.iCloudStatus != .available) } } } } /// The row's icon badge + name + caption — the same visual language as /// `RoutinesLibraryView`'s `RoutineRow`, but built from the seed's `RoutineDocument` /// rather than a live `Routine` (a not-yet-added seed has no entity to read). /// `caption` overrides the default exercise count when the row needs to explain /// itself instead. private func content(for doc: RoutineDocument, caption: String? = nil) -> some View { HStack(spacing: 12) { Image(systemName: doc.systemImage) .font(.title3) .foregroundStyle(Color.color(from: doc.color)) .frame(width: 36, height: 36) .background(Color.color(from: doc.color).opacity(0.12), in: RoundedRectangle(cornerRadius: 8)) VStack(alignment: .leading, spacing: 2) { Text(doc.name) .foregroundStyle(.primary) Text(caption ?? "\(doc.exercises.count) exercises") .font(.caption) .foregroundStyle(.secondary) } } .padding(.vertical, 2) } }