// // SettingsView.swift // Workouts // // Copyright 2025 Rouslan Zenetl. All Rights Reserved. // import SwiftUI import SwiftData import IndieAbout struct SettingsView: View { @Environment(SyncEngine.self) private var sync @Environment(AppServices.self) private var services @Query private var splits: [Split] @AppStorage("restSeconds") private var restSeconds: Int = 45 @AppStorage("doneCountdownSeconds") private var doneCountdownSeconds: Int = 5 @AppStorage("weightUnit") private var weightUnit: WeightUnit = .lb @State private var isRestoringSeeds = false @State private var restoreSeedsMessage: String? var body: some View { NavigationStack { Form { // MARK: - Workout Section Section { Stepper(value: $restSeconds, in: 10...180, step: 5) { HStack { Text("Rest Between Sets") Spacer() Text("\(restSeconds)s").foregroundColor(.secondary) } } Stepper(value: $doneCountdownSeconds, in: 3...20, step: 1) { HStack { Text("Auto-Finish Countdown") Spacer() Text("\(doneCountdownSeconds)s").foregroundColor(.secondary) } } Picker("Weight Unit", selection: $weightUnit) { ForEach(WeightUnit.allCases, id: \.self) { unit in Text(unit.displayName).tag(unit) } } } header: { Text("Workout") } footer: { Text("How long the watch waits on the finish screen before completing an exercise automatically.") } // MARK: - Library Section Section(header: Text("Library")) { NavigationLink { SplitListView() } label: { HStack { Label("Splits", systemImage: "dumbbell.fill") Spacer() Text("\(splits.count)") .foregroundStyle(.secondary) } } NavigationLink { ExerciseLibraryView() } label: { HStack { Label("Exercises", systemImage: "figure.strengthtraining.traditional") Spacer() Text("\(ExerciseMotionLibrary.exerciseNames.count)") .foregroundStyle(.secondary) } } } // MARK: - Starter Splits Section Section { Button { Task { isRestoringSeeds = true restoreSeedsMessage = nil let n = await sync.restoreSeeds() isRestoringSeeds = false restoreSeedsMessage = n == 0 ? "All starter splits are already present." : "Restored \(n) starter split\(n == 1 ? "" : "s")." } } label: { HStack { Label("Restore Starter Splits", systemImage: "arrow.counterclockwise") if isRestoringSeeds { Spacer() ProgressView() } } } .disabled(isRestoringSeeds || sync.iCloudStatus != .available) } header: { Text("Starter Splits") } footer: { if let restoreSeedsMessage { Text(restoreSeedsMessage) } else { Text("Brings back the bundled starter splits you've deleted. Splits you've edited or created are never touched.") } } // MARK: - iCloud Sync Section Section { switch sync.iCloudStatus { case .checking: Label("Connecting…", systemImage: "arrow.triangle.2.circlepath.icloud") .foregroundStyle(.secondary) case .available: Label("Connected", systemImage: "checkmark.icloud") .foregroundStyle(.secondary) case .unavailable: Label("iCloud unavailable", systemImage: "xmark.icloud") .foregroundStyle(.orange) } if let error = sync.lastSyncError { Label(error, systemImage: "exclamationmark.icloud.fill") .foregroundStyle(.orange) } } header: { Text("iCloud Sync") } // MARK: - Developer Section Section { NavigationLink { DuplicateCleanupView() } label: { Label("Clean Up Duplicates", systemImage: "wrench.and.screwdriver") } } header: { Text("Developer") } // MARK: - About Section Section { IndieAbout(configuration: AppInfoConfiguration( documents: [ .license(extension: "md") ], changelogDocument: .changelog() )) } } .navigationTitle("Settings") .onChange(of: restSeconds) { _, _ in services.watchBridge.pushAll() } .onChange(of: doneCountdownSeconds) { _, _ in services.watchBridge.pushAll() } .onChange(of: weightUnit) { _, _ in services.watchBridge.pushAll() } } } }