// // AppScenicQueueStore.swift // suixinkan // import Foundation /// 管理当前账号的景区排队打卡点、播报和设置缓存。 final class AppScenicQueueStore { private enum Key: String, CaseIterable { case punchSpotId = "_scenic_queue_punch_spot_id" case punchSpotName = "_scenic_queue_punch_spot_name" case remark = "_scenic_queue_remark" case settingsSnapshot = "_scenic_queue_settings_snapshot" case useOfflineTTS = "_scenic_queue_offline_tts_v1" case presetVoices = "_scenic_queue_preset_voices_v1" } private let defaults: UserDefaults private let session: AppSessionStore /// 使用指定持久化容器和会话创建景区排队存储。 init(defaults: UserDefaults, session: AppSessionStore) { self.defaults = defaults self.session = session } /// 已保存打卡点 ID。 var punchSpotId: String { get { string(for: .punchSpotId) } set { defaults.set(newValue, forKey: scopedKey(.punchSpotId)) } } /// 已保存打卡点名称。 var punchSpotName: String { get { string(for: .punchSpotName) } set { defaults.set(newValue, forKey: scopedKey(.punchSpotName)) } } /// 自定义播报文案。 var remark: String { get { string(for: .remark) } set { defaults.set(newValue, forKey: scopedKey(.remark)) } } /// 是否优先使用离线 TTS。 var useOfflineTTS: Bool { get { defaults.bool(forKey: scopedKey(.useOfflineTTS)) } set { defaults.set(newValue, forKey: scopedKey(.useOfflineTTS)) } } /// 当前账号是否已经保存有效打卡点。 func hasPunchSpotSaved() -> Bool { let id = punchSpotId.trimmingCharacters(in: .whitespacesAndNewlines) return Int64(id).map { $0 > 0 } ?? false } /// 保存排队设置快照。 func saveSettingsSnapshot(_ snapshot: ScenicQueueSettingsSnapshot) { guard let data = try? JSONEncoder().encode(snapshot) else { return } defaults.set(data, forKey: scopedKey(.settingsSnapshot)) } /// 读取排队设置快照。 func settingsSnapshot() -> ScenicQueueSettingsSnapshot? { guard let data = defaults.data(forKey: scopedKey(.settingsSnapshot)) else { return nil } return try? JSONDecoder().decode(ScenicQueueSettingsSnapshot.self, from: data) } /// 保存排队预设播报文案,最多 5 条。 func savePresetVoices(_ voices: [String]) { let normalized = voices .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } .prefix(5) defaults.set(Array(normalized), forKey: scopedKey(.presetVoices)) } /// 读取排队预设播报文案。 func presetVoices() -> [String] { defaults.stringArray(forKey: scopedKey(.presetVoices)) ?? [] } /// 清除当前账号的景区排队缓存。 func clear() { Key.allCases.forEach { defaults.removeObject(forKey: scopedKey($0)) } } private func string(for key: Key) -> String { defaults.string(forKey: scopedKey(key)) ?? "" } private func scopedKey(_ key: Key) -> String { "\(session.accountCachePrefix)_\(key.rawValue)" } }