import Foundation import Combine final class SettingsStore: ObservableObject { @Published var serverURL: String { didSet { AppPreferences.store.set(serverURL, forKey: Keys.serverURL) } } @Published var voiceID: String { didSet { AppPreferences.store.set(voiceID, forKey: Keys.voiceID) } } @Published var textMode: Bool { didSet { AppPreferences.store.set(textMode, forKey: Keys.textMode) } } private enum Keys { static let serverURL = "serverURL" static let voiceID = "voiceID" static let textMode = "textMode" } static let defaultServerURL = "https://192.168.86.2:9178" init() { let d = AppPreferences.store // Sanitize legacy values: earlier versions wrote the field to storage on // every keystroke, so an invalid half-typed URL could be persisted and // shipped to the network layer. Fall back to the documented default and // repair the stored value once. let storedURL = d.string(forKey: Keys.serverURL) ?? Self.defaultServerURL switch ServerConfig.parse(storedURL) { case .success: serverURL = storedURL case .failure: // Legacy invalid value: repair to the documented default. serverURL = Self.defaultServerURL d.set(Self.defaultServerURL, forKey: Keys.serverURL) } let storedVoice = d.string(forKey: Keys.voiceID) voiceID = Voices.all.contains { $0.id == storedVoice } ? (storedVoice ?? "") : Voices.defaultID textMode = d.bool(forKey: Keys.textMode) } var trimmedServerURL: String { serverURL.trimmingCharacters(in: .whitespacesAndNewlines) } } enum AppPreferences { static let store: UserDefaults = { #if DEBUG if ProcessInfo.processInfo.arguments.contains("--ui-testing") { let suite = "dev.shane.voice.ui-testing" let defaults = UserDefaults(suiteName: suite)! if ProcessInfo.processInfo.arguments.contains("--reset-ui-testing") { defaults.removePersistentDomain(forName: suite) // Deterministic offline errors; UI tests must not contact the NAS. defaults.set("https://127.0.0.1:1", forKey: "serverURL") } if ProcessInfo.processInfo.arguments.contains("--live-testing") { defaults.set(SettingsStore.defaultServerURL, forKey: "serverURL") defaults.set(true, forKey: "textMode") } return defaults } #endif return .standard }() }