// ── Routine List View ───────────────────────────────────────── // Displays built-in templates and user routines with start/edit actions import SwiftUI struct RoutineListView: View { @State private var routines: [CMRoutine] = RoutineTemplates.all @State private var activeRoutine: CMRoutine? @State private var showEditor = false @State private var editingRoutine: CMRoutine? var body: some View { ScrollView { VStack(spacing: 20) { // Active routine banner if let active = activeRoutine { activeRoutineBanner(active) } // Templates section sectionHeader("Templates") ForEach(routines.filter { $0.isTemplate }) { routine in routineCard(routine) } // User routines section let userRoutines = routines.filter { !$0.isTemplate } if !userRoutines.isEmpty { sectionHeader("My Routines") ForEach(userRoutines) { routine in routineCard(routine) } } } .padding() } .background(CMColors.bg.ignoresSafeArea()) .navigationTitle("Routines") .toolbar { ToolbarItem(placement: .primaryAction) { Button { editingRoutine = nil showEditor = true } label: { Image(systemName: "plus") .foregroundColor(CMColors.accent) } } } .sheet(isPresented: $showEditor) { RoutineEditorView( routine: editingRoutine, onSave: { routine in if let idx = routines.firstIndex(where: { $0.id == routine.id }) { routines[idx] = routine } else { routines.append(routine) } showEditor = false }, onCancel: { showEditor = false } ) } .fullScreenCover(item: $activeRoutine) { routine in RoutineRunnerView( routine: routine, onComplete: { completed in activeRoutine = nil if let idx = routines.firstIndex(where: { $0.id == completed.id }) { routines[idx] = completed } }, onCancel: { activeRoutine = nil } ) } } // MARK: - Subviews @ViewBuilder private func sectionHeader(_ title: String) -> some View { HStack { Text(title) .font(.headline) .foregroundColor(CMColors.textSecondary) Spacer() } } @ViewBuilder private func routineCard(_ routine: CMRoutine) -> some View { VStack(alignment: .leading, spacing: 8) { HStack { VStack(alignment: .leading, spacing: 4) { Text(routine.name) .font(.title3.bold()) .foregroundColor(CMColors.text) if let desc = routine.routineDescription { Text(desc) .font(.subheadline) .foregroundColor(CMColors.textSecondary) } } Spacer() VStack(alignment: .trailing, spacing: 2) { Text("\(routine.totalDurationMinutes) min") .font(.caption.bold()) .foregroundColor(CMColors.accent) Text("\(routine.steps.count) steps") .font(.caption2) .foregroundColor(CMColors.textMuted) } } // Step preview HStack(spacing: 4) { ForEach(routine.steps.prefix(5)) { step in Text(step.label) .font(.caption2) .padding(.horizontal, 6) .padding(.vertical, 2) .background(CMColors.surfaceHover) .cornerRadius(4) .foregroundColor(CMColors.textSecondary) } if routine.steps.count > 5 { Text("+\(routine.steps.count - 5)") .font(.caption2) .foregroundColor(CMColors.textMuted) } } // Actions HStack(spacing: 12) { Button { var instance = routine.isTemplate ? routine.instantiate() : routine instance.start() activeRoutine = instance } label: { Label("Start", systemImage: "play.fill") .font(.subheadline.bold()) .foregroundColor(.white) .padding(.horizontal, 16) .padding(.vertical, 8) .background(CMColors.accent) .cornerRadius(8) } if !routine.isTemplate { Button { editingRoutine = routine showEditor = true } label: { Label("Edit", systemImage: "pencil") .font(.subheadline) .foregroundColor(CMColors.textSecondary) } } } .padding(.top, 4) } .padding() .background(CMColors.surface) .cornerRadius(12) } @ViewBuilder private func activeRoutineBanner(_ routine: CMRoutine) -> some View { HStack { VStack(alignment: .leading, spacing: 2) { Text("Active Routine") .font(.caption.bold()) .foregroundColor(CMColors.accent) Text(routine.name) .font(.headline) .foregroundColor(CMColors.text) if let step = routine.currentStep { Text("Step: \(step.label)") .font(.subheadline) .foregroundColor(CMColors.textSecondary) } } Spacer() Button("Resume") { activeRoutine = routine } .font(.subheadline.bold()) .padding(.horizontal, 16) .padding(.vertical, 8) .background(CMColors.accent) .foregroundColor(.white) .cornerRadius(8) } .padding() .background( RoundedRectangle(cornerRadius: 12) .fill(CMColors.surface) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(CMColors.accent.opacity(0.3), lineWidth: 1) ) ) } }