统一账号作用域 Key 对齐 Android,新增缓存 schema 迁移清理旧数据,并同步更新相关模块与单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
132 lines
4.4 KiB
Swift
132 lines
4.4 KiB
Swift
//
|
|
// 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 {
|
|
guard let key = scenicKey(.punchSpotId) else { return "" }
|
|
return defaults.string(forKey: key) ?? ""
|
|
}
|
|
set {
|
|
guard let key = scenicKey(.punchSpotId) else { return }
|
|
defaults.set(newValue, forKey: key)
|
|
}
|
|
}
|
|
|
|
/// 已保存打卡点名称。
|
|
var punchSpotName: String {
|
|
get { spotString(for: .punchSpotName) }
|
|
set { setSpotString(newValue, for: .punchSpotName) }
|
|
}
|
|
|
|
/// 自定义播报文案。
|
|
var remark: String {
|
|
get { spotString(for: .remark) }
|
|
set { setSpotString(newValue, for: .remark) }
|
|
}
|
|
|
|
/// 是否优先使用离线 TTS。
|
|
var useOfflineTTS: Bool {
|
|
get {
|
|
guard let key = scenicKey(.useOfflineTTS),
|
|
defaults.object(forKey: key) != nil else { return true }
|
|
return defaults.bool(forKey: key)
|
|
}
|
|
set {
|
|
guard let key = scenicKey(.useOfflineTTS) else { return }
|
|
defaults.set(newValue, forKey: key)
|
|
}
|
|
}
|
|
|
|
/// 当前账号是否已经保存有效打卡点。
|
|
func hasPunchSpotSaved() -> Bool {
|
|
let id = punchSpotId.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return Int64(id).map { $0 > 0 } ?? false
|
|
}
|
|
|
|
/// 保存排队设置快照。
|
|
func saveSettingsSnapshot(_ snapshot: ScenicQueueSettingsSnapshot) {
|
|
guard let key = spotKey(.settingsSnapshot),
|
|
let data = try? JSONEncoder().encode(snapshot) else { return }
|
|
defaults.set(data, forKey: key)
|
|
}
|
|
|
|
/// 读取排队设置快照。
|
|
func settingsSnapshot() -> ScenicQueueSettingsSnapshot? {
|
|
guard let key = spotKey(.settingsSnapshot),
|
|
let data = defaults.data(forKey: key) else { return nil }
|
|
return try? JSONDecoder().decode(ScenicQueueSettingsSnapshot.self, from: data)
|
|
}
|
|
|
|
/// 保存排队预设播报文案,最多 5 条。
|
|
func savePresetVoices(_ voices: [String]) {
|
|
guard let key = spotKey(.presetVoices) else { return }
|
|
let normalized = voices
|
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
.filter { !$0.isEmpty }
|
|
.prefix(5)
|
|
defaults.set(Array(normalized), forKey: key)
|
|
}
|
|
|
|
/// 读取排队预设播报文案。
|
|
func presetVoices() -> [String] {
|
|
guard let key = spotKey(.presetVoices) else { return [] }
|
|
return defaults.stringArray(forKey: key) ?? []
|
|
}
|
|
|
|
/// 清除当前账号的景区排队缓存。
|
|
func clear(accountScope: String) {
|
|
guard !accountScope.isEmpty else { return }
|
|
let scenicPrefix = "\(accountScope)_scenic_"
|
|
let suffixes = Key.allCases.map { "_\($0.rawValue)" }
|
|
defaults.dictionaryRepresentation().keys
|
|
.filter { key in
|
|
key.hasPrefix(scenicPrefix) && suffixes.contains(where: key.hasSuffix)
|
|
}
|
|
.forEach { defaults.removeObject(forKey: $0) }
|
|
}
|
|
|
|
private func scenicKey(_ key: Key) -> String? {
|
|
session.scenicScopedKey(key.rawValue)
|
|
}
|
|
|
|
private func spotKey(_ key: Key) -> String? {
|
|
session.scenicSpotScopedKey(key.rawValue, spotId: punchSpotId)
|
|
}
|
|
|
|
private func spotString(for key: Key) -> String {
|
|
guard let scopedKey = spotKey(key) else { return "" }
|
|
return defaults.string(forKey: scopedKey) ?? ""
|
|
}
|
|
|
|
private func setSpotString(_ value: String, for key: Key) {
|
|
guard let scopedKey = spotKey(key) else { return }
|
|
defaults.set(value, forKey: scopedKey)
|
|
}
|
|
}
|