// // WorkoutSettings.swift // Keging // // 세트 구성(수축·이완·횟수)과 진동 패턴 — 폰에서 설정, 워치로 동기화 // import Foundation import Combine /// 진동 패턴 — 수축/이완 각각 선택. 진동 횟수·리듬으로 구분한다 nonisolated enum HapticPattern: String, CaseIterable, Identifiable, Codable { case once, twice, thrice, heartbeat var id: String { rawValue } var label: String { switch self { case .once: String(localized: "한 번") case .twice: String(localized: "두 번") case .thrice: String(localized: "세 번") case .heartbeat: String(localized: "심장 박동") } } /// 각 진동이 울리는 시점(초) — 첫 진동은 0 var beatOffsets: [TimeInterval] { switch self { case .once: [0] case .twice: [0, 0.45] case .thrice: [0, 0.45, 0.9] case .heartbeat: [0, 0.3, 1.0, 1.3] } } } /// 세트 구성 — 세트 = (수축 → 이완) × 횟수 nonisolated struct WorkoutConfig: Codable, Equatable { var contractSeconds: Int = 10 var relaxSeconds: Int = 5 var reps: Int = 20 var contractPattern: HapticPattern = .twice var relaxPattern: HapticPattern = .once /// 완주 시 애플 건강에 함께 기록할 운동 강도 (0 = 기록 안 함, 1~10) var effortScore: Int = 2 init( contractSeconds: Int = 10, relaxSeconds: Int = 5, reps: Int = 20, contractPattern: HapticPattern = .twice, relaxPattern: HapticPattern = .once, effortScore: Int = 2 ) { self.contractSeconds = contractSeconds self.relaxSeconds = relaxSeconds self.reps = reps self.contractPattern = contractPattern self.relaxPattern = relaxPattern self.effortScore = effortScore } // 필드 추가에도 기존 저장 JSON이 깨지지 않게 전 필드 decodeIfPresent init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) contractSeconds = try container.decodeIfPresent(Int.self, forKey: .contractSeconds) ?? 10 relaxSeconds = try container.decodeIfPresent(Int.self, forKey: .relaxSeconds) ?? 5 reps = try container.decodeIfPresent(Int.self, forKey: .reps) ?? 20 contractPattern = try container.decodeIfPresent(HapticPattern.self, forKey: .contractPattern) ?? .twice relaxPattern = try container.decodeIfPresent(HapticPattern.self, forKey: .relaxPattern) ?? .once effortScore = try container.decodeIfPresent(Int.self, forKey: .effortScore) ?? 2 } var totalDuration: TimeInterval { Double(reps * (contractSeconds + relaxSeconds)) } var summaryText: String { String(localized: "수축 \(contractSeconds)초 · 이완 \(relaxSeconds)초 · \(reps)회") } } @MainActor final class SettingsStore: ObservableObject { static let shared = SettingsStore() @Published var config: WorkoutConfig { didSet { save() if config != oldValue { onConfigChange?(config) } } } /// 폰 → 워치 동기화 훅 (iOS에서만 연결) var onConfigChange: ((WorkoutConfig) -> Void)? private static let key = "workout.config.v1" private init() { if let data = AppGroup.defaults.data(forKey: Self.key), let saved = try? JSONDecoder().decode(WorkoutConfig.self, from: data) { config = saved } else { config = WorkoutConfig() } } private func save() { if let data = try? JSONEncoder().encode(config) { AppGroup.defaults.set(data, forKey: Self.key) } } }