// // StarterPreviewView.swift // Workouts // // Copyright 2026 Rouslan Zenetl. All Rights Reserved. // import SwiftUI import SwiftData /// Read-only preview of one bundled starter routine — its icon, activity type, and /// full exercise plan, exactly as it would land in the library — with the action that /// actually installs it. Pushed from `StarterGalleryView` for a seed that isn't /// already a live routine (or, read-only, for one blocked by a name collision). struct StarterPreviewView: View { @Environment(SyncEngine.self) private var sync @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) .navigationBarTitleDisplayMode(.inline) } // MARK: - Header private var header: some View { HStack(spacing: 12) { 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) } } } // MARK: - Exercises private var exerciseList: some View { VStack(alignment: .leading, spacing: 14) { Text("Exercises") .font(.headline) ForEach(seed.doc.exercises.sorted(by: { $0.order < $1.order })) { exercise in VStack(alignment: .leading, spacing: 2) { Text(exercise.name) Text(exercise.planSummary(weightUnit: weightUnit)) .font(.caption) .foregroundStyle(.secondary) } } } } // MARK: - Action /// The prominent bottom action, reflecting the same three states the gallery row /// does: already added (a link to the live routine), blocked by a name collision /// (disabled, with the reason), or free to restore (active). @ViewBuilder private var actionBar: some View { switch state { case .added(let routine): NavigationLink { RoutineDetailView(routine: routine) } label: { Label("Added — View Routine", systemImage: "checkmark.circle.fill") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .tint(.green) .padding() .background(.thinMaterial) case .nameTaken: VStack(spacing: 6) { Text("Add to My Routines") .frame(maxWidth: .infinity) .padding(.vertical, 10) .background(Color.secondary.opacity(0.15), in: RoundedRectangle(cornerRadius: 10)) .foregroundStyle(.secondary) Text("A routine named \"\(seed.doc.name)\" already exists.") .font(.caption) .foregroundStyle(.secondary) } .padding() .background(.thinMaterial) case .restorable: Button { Task { isAdding = true let added = await sync.restoreSeed(id: seed.id) isAdding = false // The @Query reflects the new routine the instant the cache mirror // lands, so popping back to the gallery (which now shows this seed // as Added) is enough — no local flag needed to fake that state here. if added { dismiss() } } } label: { HStack { if isAdding { ProgressView().tint(.white) } Text(isAdding ? "Adding…" : "Add to My Routines") } .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .disabled(isAdding || sync.iCloudStatus != .available) .padding() .background(.thinMaterial) } } }