模块化 AppStore 并完善素材管理与个人空间设置。

将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 11:01:08 +08:00
parent ceca780ab3
commit 005349f8e6
128 changed files with 2953 additions and 1123 deletions

View 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)"
}
}