Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md. Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
8.5 KiB
Swift
176 lines
8.5 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)")
|
||
}
|
||
|
||
/// 规范化dUserId格式。
|
||
private static func normalizedUserId(_ userId: String?) -> String {
|
||
let text = userId?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||
return text.isEmpty ? "anonymous" : text
|
||
}
|
||
|
||
/// positive整数相关逻辑。
|
||
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
|
||
}
|
||
|
||
/// legacy正数整数相关逻辑。
|
||
private static func legacyPositiveInt(forKey key: String) -> Int? {
|
||
positiveInt(forKey: key)
|
||
}
|
||
}
|