173 lines
8.4 KiB
Swift
173 lines
8.4 KiB
Swift
//
|
||
// ScenicQueueSettingsStore.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/25.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 排队管理本地设置 key。
|
||
enum ScenicQueueLocalSettings {
|
||
static let selectedSpotIdKey = "scenic_queue_selected_spot_id"
|
||
static let selectedSpotNameKey = "scenic_queue_selected_spot_name"
|
||
static let customTtsTextKey = "scenic_queue_custom_tts_text"
|
||
static let settingsSnapshotKey = "scenic_queue_settings_snapshot"
|
||
static let photoEstimateSecondsKey = "scenic_queue_photo_estimate_seconds"
|
||
static let broadcastIntervalSecondsKey = "scenic_queue_broadcast_interval_seconds"
|
||
static let countdownThresholdSecondsKey = "scenic_queue_countdown_threshold_seconds"
|
||
static let showStartShootingButtonKey = "scenic_queue_show_start_shooting_button"
|
||
static let autoCallAheadCountKey = "scenic_queue_auto_call_ahead_count"
|
||
static let quickCallButtonEnabledKey = "scenic_queue_quick_call_button_enabled"
|
||
static let prepareCallButtonEnabledKey = "scenic_queue_prepare_call_button_enabled"
|
||
static let presetVoicesKey = "scenic_queue_preset_voices"
|
||
static let ttsEnabledKey = "scenic_queue_tts_enabled"
|
||
static let backgroundPollEnabledKey = "scenic_queue_background_poll_enabled"
|
||
}
|
||
|
||
/// 排队管理本地设置存储,按用户、景区、打卡点隔离并兼容旧 key。
|
||
enum ScenicQueueSettingsStore {
|
||
/// 生成景区作用域 key。
|
||
static func scopedKey(base: String, userId: String?, scenicId: Int?) -> String {
|
||
guard let scenicId else { return base }
|
||
let user = normalizedUserId(userId)
|
||
return "\(base)_user_\(user)_scenic_\(scenicId)"
|
||
}
|
||
|
||
/// 生成打卡点作用域 key。
|
||
static func scopedSpotKey(base: String, userId: String?, scenicId: Int?, spotId: Int?) -> String {
|
||
guard let scenicId, let spotId else { return scopedKey(base: base, userId: userId, scenicId: scenicId) }
|
||
let user = normalizedUserId(userId)
|
||
return "\(base)_user_\(user)_scenic_\(scenicId)_spot_\(spotId)"
|
||
}
|
||
|
||
/// 读取已选打卡点 ID。
|
||
static func selectedSpotId(userId: String?, scenicId: Int?) -> Int? {
|
||
guard let scenicId else { return legacyPositiveInt(forKey: ScenicQueueLocalSettings.selectedSpotIdKey) }
|
||
let key = scopedKey(base: ScenicQueueLocalSettings.selectedSpotIdKey, userId: userId, scenicId: scenicId)
|
||
if let value = positiveInt(forKey: key) { return value }
|
||
if let legacy = legacyPositiveInt(forKey: ScenicQueueLocalSettings.selectedSpotIdKey) {
|
||
UserDefaults.standard.set(legacy, forKey: key)
|
||
return legacy
|
||
}
|
||
return nil
|
||
}
|
||
|
||
/// 读取已选打卡点名称。
|
||
static func selectedSpotName(userId: String?, scenicId: Int?) -> String {
|
||
guard let scenicId else {
|
||
return UserDefaults.standard.string(forKey: ScenicQueueLocalSettings.selectedSpotNameKey) ?? ""
|
||
}
|
||
let key = scopedKey(base: ScenicQueueLocalSettings.selectedSpotNameKey, userId: userId, scenicId: scenicId)
|
||
if let value = UserDefaults.standard.string(forKey: key), !value.isEmpty { return value }
|
||
let legacy = UserDefaults.standard.string(forKey: ScenicQueueLocalSettings.selectedSpotNameKey) ?? ""
|
||
if !legacy.isEmpty {
|
||
UserDefaults.standard.set(legacy, forKey: key)
|
||
}
|
||
return legacy
|
||
}
|
||
|
||
/// 保存已选打卡点。
|
||
static func saveSelectedSpot(id: Int, name: String, userId: String?, scenicId: Int) {
|
||
let defaults = UserDefaults.standard
|
||
defaults.set(id, forKey: ScenicQueueLocalSettings.selectedSpotIdKey)
|
||
defaults.set(name, forKey: ScenicQueueLocalSettings.selectedSpotNameKey)
|
||
defaults.set(id, forKey: scopedKey(base: ScenicQueueLocalSettings.selectedSpotIdKey, userId: userId, scenicId: scenicId))
|
||
defaults.set(name, forKey: scopedKey(base: ScenicQueueLocalSettings.selectedSpotNameKey, userId: userId, scenicId: scenicId))
|
||
}
|
||
|
||
/// 读取自定义语音文本。
|
||
static func customTtsText(userId: String?, scenicId: Int?, spotId: Int?) -> String {
|
||
guard let scenicId, let spotId else {
|
||
return UserDefaults.standard.string(forKey: ScenicQueueLocalSettings.customTtsTextKey) ?? ""
|
||
}
|
||
let key = scopedSpotKey(base: ScenicQueueLocalSettings.customTtsTextKey, userId: userId, scenicId: scenicId, spotId: spotId)
|
||
if let value = UserDefaults.standard.string(forKey: key) { return value }
|
||
let legacy = UserDefaults.standard.string(forKey: ScenicQueueLocalSettings.customTtsTextKey) ?? ""
|
||
if !legacy.isEmpty {
|
||
UserDefaults.standard.set(legacy, forKey: key)
|
||
}
|
||
return legacy
|
||
}
|
||
|
||
/// 保存自定义语音文本。
|
||
static func saveCustomTtsText(_ text: String, userId: String?, scenicId: Int?, spotId: Int?) {
|
||
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
UserDefaults.standard.set(trimmed, forKey: ScenicQueueLocalSettings.customTtsTextKey)
|
||
if let scenicId, let spotId {
|
||
UserDefaults.standard.set(
|
||
trimmed,
|
||
forKey: scopedSpotKey(base: ScenicQueueLocalSettings.customTtsTextKey, userId: userId, scenicId: scenicId, spotId: spotId)
|
||
)
|
||
}
|
||
}
|
||
|
||
/// 读取设置快照。
|
||
static func settingsSnapshot(userId: String?, scenicId: Int, spotId: Int) -> ScenicQueueSettingsSnapshot? {
|
||
let keys = [
|
||
scopedSpotKey(base: ScenicQueueLocalSettings.settingsSnapshotKey, userId: userId, scenicId: scenicId, spotId: spotId),
|
||
"scenic_\(scenicId)_spot_\(spotId)_\(ScenicQueueLocalSettings.settingsSnapshotKey)",
|
||
"scenic_\(scenicId)_\(ScenicQueueLocalSettings.settingsSnapshotKey)"
|
||
]
|
||
for key in keys {
|
||
guard let data = UserDefaults.standard.data(forKey: key),
|
||
let snapshot = try? JSONDecoder().decode(ScenicQueueSettingsSnapshot.self, from: data)
|
||
else { continue }
|
||
return snapshot
|
||
}
|
||
return nil
|
||
}
|
||
|
||
/// 保存设置快照。
|
||
static func saveSettingsSnapshot(_ snapshot: ScenicQueueSettingsSnapshot, userId: String?, scenicId: Int, spotId: Int) {
|
||
guard let data = try? JSONEncoder().encode(snapshot) else { return }
|
||
UserDefaults.standard.set(
|
||
data,
|
||
forKey: scopedSpotKey(base: ScenicQueueLocalSettings.settingsSnapshotKey, userId: userId, scenicId: scenicId, spotId: spotId)
|
||
)
|
||
UserDefaults.standard.set(data, forKey: "scenic_\(scenicId)_spot_\(spotId)_\(ScenicQueueLocalSettings.settingsSnapshotKey)")
|
||
}
|
||
|
||
/// 读取语音预设。
|
||
static func presetVoices(userId: String?, scenicId: Int?, spotId: Int?) -> [String] {
|
||
guard let scenicId, let spotId else { return [] }
|
||
let keys = [
|
||
scopedSpotKey(base: ScenicQueueLocalSettings.presetVoicesKey, userId: userId, scenicId: scenicId, spotId: spotId),
|
||
"scenic_\(scenicId)_spot_\(spotId)_\(ScenicQueueLocalSettings.presetVoicesKey)"
|
||
]
|
||
for key in keys {
|
||
guard let data = UserDefaults.standard.data(forKey: key),
|
||
let voices = try? JSONDecoder().decode([String].self, from: data)
|
||
else { continue }
|
||
return voices
|
||
}
|
||
return []
|
||
}
|
||
|
||
/// 保存语音预设。
|
||
static func savePresetVoices(_ voices: [String], userId: String?, scenicId: Int, spotId: Int) {
|
||
let normalized = Array(voices.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty }.prefix(5))
|
||
guard let data = try? JSONEncoder().encode(normalized) else { return }
|
||
UserDefaults.standard.set(
|
||
data,
|
||
forKey: scopedSpotKey(base: ScenicQueueLocalSettings.presetVoicesKey, userId: userId, scenicId: scenicId, spotId: spotId)
|
||
)
|
||
UserDefaults.standard.set(data, forKey: "scenic_\(scenicId)_spot_\(spotId)_\(ScenicQueueLocalSettings.presetVoicesKey)")
|
||
}
|
||
|
||
private static func normalizedUserId(_ userId: String?) -> String {
|
||
let text = userId?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||
return text.isEmpty ? "anonymous" : text
|
||
}
|
||
|
||
private static func positiveInt(forKey key: String) -> Int? {
|
||
guard UserDefaults.standard.object(forKey: key) != nil else { return nil }
|
||
let value = UserDefaults.standard.integer(forKey: key)
|
||
return value > 0 ? value : nil
|
||
}
|
||
|
||
private static func legacyPositiveInt(forKey key: String) -> Int? {
|
||
positiveInt(forKey: key)
|
||
}
|
||
}
|