新增景区排队管理功能

This commit is contained in:
2026-07-07 15:32:25 +08:00
parent 854a66689f
commit 0aa8b14e1f
20 changed files with 4642 additions and 1 deletions
+80
View File
@@ -34,6 +34,12 @@ final class AppStore {
static let lastLocationReportTime = "key_last_location_report_time"
static let locationReminderMinutes = "key_location_reminder_minutes"
static let isOpenReceiveVoice = "key_is_open_receive_voice"
static let scenicQueuePunchSpotId = "_scenic_queue_punch_spot_id"
static let scenicQueuePunchSpotName = "_scenic_queue_punch_spot_name"
static let scenicQueueRemark = "_scenic_queue_remark"
static let scenicQueueSettingsSnapshot = "_scenic_queue_settings_snapshot"
static let scenicQueueOfflineTTS = "_scenic_queue_offline_tts_v1"
static let scenicQueuePresetVoices = "_scenic_queue_preset_voices_v1"
}
private let defaults: UserDefaults
@@ -161,6 +167,30 @@ final class AppStore {
set { defaults.set(newValue, forKey: accountScopedKey(Key.isOpenReceiveVoice)) }
}
/// 排队管理已保存打卡点 ID。
var scenicQueuePunchSpotId: String {
get { defaults.string(forKey: accountScopedKey(Key.scenicQueuePunchSpotId)) ?? "" }
set { defaults.set(newValue, forKey: accountScopedKey(Key.scenicQueuePunchSpotId)) }
}
/// 排队管理已保存打卡点名称。
var scenicQueuePunchSpotName: String {
get { defaults.string(forKey: accountScopedKey(Key.scenicQueuePunchSpotName)) ?? "" }
set { defaults.set(newValue, forKey: accountScopedKey(Key.scenicQueuePunchSpotName)) }
}
/// 排队管理自定义播报文案。
var scenicQueueRemark: String {
get { defaults.string(forKey: accountScopedKey(Key.scenicQueueRemark)) ?? "" }
set { defaults.set(newValue, forKey: accountScopedKey(Key.scenicQueueRemark)) }
}
/// 排队语音是否优先使用离线 TTS。
var scenicQueueUseOfflineTTS: Bool {
get { defaults.bool(forKey: accountScopedKey(Key.scenicQueueOfflineTTS)) }
set { defaults.set(newValue, forKey: accountScopedKey(Key.scenicQueueOfflineTTS)) }
}
/// 当前账号缓存前缀,对齐 Android `getAccountCachePrefix`。
var accountCachePrefix: String {
let uid = userId.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -306,6 +336,50 @@ final class AppStore {
defaults.removeObject(forKey: accountScopedKey(Key.lastLocationReportTime))
}
/// 是否已经保存排队打卡点。
func hasScenicQueuePunchSpotSaved() -> Bool {
let id = scenicQueuePunchSpotId.trimmingCharacters(in: .whitespacesAndNewlines)
return Int64(id).map { $0 > 0 } ?? false
}
/// 保存排队设置快照。
func saveScenicQueueSettingsSnapshot(_ snapshot: ScenicQueueSettingsSnapshot) {
guard let data = try? JSONEncoder().encode(snapshot) else { return }
defaults.set(data, forKey: accountScopedKey(Key.scenicQueueSettingsSnapshot))
}
/// 读取排队设置快照。
func scenicQueueSettingsSnapshot() -> ScenicQueueSettingsSnapshot? {
guard let data = defaults.data(forKey: accountScopedKey(Key.scenicQueueSettingsSnapshot)) else { return nil }
return try? JSONDecoder().decode(ScenicQueueSettingsSnapshot.self, from: data)
}
/// 保存排队预设播报文案,最多 5 条。
func saveScenicQueuePresetVoices(_ voices: [String]) {
let normalized = voices
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
.prefix(5)
defaults.set(Array(normalized), forKey: accountScopedKey(Key.scenicQueuePresetVoices))
}
/// 读取排队预设播报文案。
func scenicQueuePresetVoices() -> [String] {
defaults.stringArray(forKey: accountScopedKey(Key.scenicQueuePresetVoices)) ?? []
}
/// 清除排队管理本地缓存。
func clearScenicQueueSnapshot() {
[
Key.scenicQueuePunchSpotId,
Key.scenicQueuePunchSpotName,
Key.scenicQueueRemark,
Key.scenicQueueSettingsSnapshot,
Key.scenicQueueOfflineTTS,
Key.scenicQueuePresetVoices,
].forEach { defaults.removeObject(forKey: accountScopedKey($0)) }
}
/// 清除权限与位置会话快照。
func clearPermissionSnapshot() {
let prefix = accountScopedKey("")
@@ -317,6 +391,12 @@ final class AppStore {
Key.lastLocationReportTime,
Key.locationReminderMinutes,
Key.isOpenReceiveVoice,
Key.scenicQueuePunchSpotId,
Key.scenicQueuePunchSpotName,
Key.scenicQueueRemark,
Key.scenicQueueSettingsSnapshot,
Key.scenicQueueOfflineTTS,
Key.scenicQueuePresetVoices,
]
keys.forEach { defaults.removeObject(forKey: prefix + $0) }
}