模块化 AppStore 并完善素材管理与个人空间设置。
将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
97
suixinkan/DataStore/AppScenicQueueStore.swift
Normal file
97
suixinkan/DataStore/AppScenicQueueStore.swift
Normal file
@ -0,0 +1,97 @@
|
||||
//
|
||||
// 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)"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user