模块化 AppStore 并完善素材管理与个人空间设置。
将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -39,7 +39,7 @@ final class ScenicQueueSettingChangeLogViewModel {
|
||||
}
|
||||
|
||||
private func load(api: ScenicQueueAPIProtocol, page: Int, append: Bool) async {
|
||||
guard appStore.currentScenicId > 0 else {
|
||||
guard appStore.session.currentScenicId > 0 else {
|
||||
onShowMessage?("请先选择景区")
|
||||
return
|
||||
}
|
||||
@ -53,10 +53,10 @@ final class ScenicQueueSettingChangeLogViewModel {
|
||||
if append { isLoadingMore = false } else { isRefreshing = false }
|
||||
notifyStateChange()
|
||||
}
|
||||
let spotId = Int64(appStore.scenicQueuePunchSpotId)
|
||||
let spotId = Int64(appStore.scenicQueue.punchSpotId)
|
||||
do {
|
||||
let data = try await api.settingChangeLog(
|
||||
scenicId: Int64(appStore.currentScenicId),
|
||||
scenicId: Int64(appStore.session.currentScenicId),
|
||||
scenicSpotId: spotId,
|
||||
page: page,
|
||||
pageSize: Constants.pageSize
|
||||
|
||||
@ -61,9 +61,9 @@ final class ScenicQueueSettingsViewModel {
|
||||
init(appStore: AppStore = .shared, ttsManager: ScenicQueueTTSManaging = AliyunNLSTTSManager.shared) {
|
||||
self.appStore = appStore
|
||||
self.ttsManager = ttsManager
|
||||
let snapshot = appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
selectedPunchSpotId = appStore.scenicQueuePunchSpotId.nonEmptyTrimmed
|
||||
selectedPunchSpotLabel = appStore.scenicQueuePunchSpotName
|
||||
let snapshot = appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
selectedPunchSpotId = appStore.scenicQueue.punchSpotId.nonEmptyTrimmed
|
||||
selectedPunchSpotLabel = appStore.scenicQueue.punchSpotName
|
||||
shootMinute = snapshot.shootMinute.clamped(to: Limits.shootMinute)
|
||||
shootSecond = snapshot.shootSecond.clamped(to: Limits.shootSecond)
|
||||
firstAheadCount = snapshot.firstAheadCount.clamped(to: Limits.ahead)
|
||||
@ -84,8 +84,8 @@ final class ScenicQueueSettingsViewModel {
|
||||
businessOpen = snapshot.businessOpen
|
||||
businessStartTime = Self.normalizedTime(snapshot.businessStartTime, fallback: "10:00")
|
||||
businessEndTime = Self.normalizedTime(snapshot.businessEndTime, fallback: "20:00")
|
||||
customTTSText = appStore.scenicQueueRemark
|
||||
presetVoices = appStore.scenicQueuePresetVoices()
|
||||
customTTSText = appStore.scenicQueue.remark
|
||||
presetVoices = appStore.scenicQueue.presetVoices()
|
||||
}
|
||||
|
||||
var selectedPunchSpotDisplayText: String {
|
||||
@ -105,7 +105,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
|
||||
/// 是否优先使用离线语音合成。
|
||||
var useOfflineTTS: Bool {
|
||||
appStore.scenicQueueUseOfflineTTS
|
||||
appStore.scenicQueue.useOfflineTTS
|
||||
}
|
||||
|
||||
func openSettingChangeLog() {
|
||||
@ -114,12 +114,12 @@ final class ScenicQueueSettingsViewModel {
|
||||
|
||||
/// 打开打卡点选择器前加载全部打卡点。
|
||||
func loadPunchSpotOptions(api: ScenicQueueAPIProtocol) async {
|
||||
guard appStore.currentScenicId > 0 else {
|
||||
guard appStore.session.currentScenicId > 0 else {
|
||||
onShowMessage?("请先选择景区")
|
||||
return
|
||||
}
|
||||
do {
|
||||
let response = try await api.scenicSpotListAll(scenicId: String(appStore.currentScenicId))
|
||||
let response = try await api.scenicSpotListAll(scenicId: String(appStore.session.currentScenicId))
|
||||
punchSpotOptions = response.list.map {
|
||||
ScenicQueuePunchSpotOption(id: String($0.id), label: $0.name)
|
||||
}
|
||||
@ -178,17 +178,17 @@ final class ScenicQueueSettingsViewModel {
|
||||
/// 设置离线语音合成偏好。
|
||||
func setUseOfflineTTS(_ enabled: Bool) {
|
||||
guard enabled else {
|
||||
appStore.scenicQueueUseOfflineTTS = false
|
||||
appStore.scenicQueue.useOfflineTTS = false
|
||||
notifyStateChange()
|
||||
return
|
||||
}
|
||||
guard ttsManager.supportsOfflineTTS else {
|
||||
appStore.scenicQueueUseOfflineTTS = false
|
||||
appStore.scenicQueue.useOfflineTTS = false
|
||||
onShowMessage?("离线语音资源未安装")
|
||||
notifyStateChange()
|
||||
return
|
||||
}
|
||||
appStore.scenicQueueUseOfflineTTS = true
|
||||
appStore.scenicQueue.useOfflineTTS = true
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
@ -234,7 +234,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
|
||||
/// 保存当前文案到本地预设。
|
||||
func saveCurrentTextAsPresetVoiceLocal() {
|
||||
guard appStore.currentScenicId > 0, selectedPunchSpotId?.nonEmptyTrimmed != nil else {
|
||||
guard appStore.session.currentScenicId > 0, selectedPunchSpotId?.nonEmptyTrimmed != nil else {
|
||||
onShowMessage?("请先选择打卡点")
|
||||
return
|
||||
}
|
||||
@ -252,7 +252,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
return
|
||||
}
|
||||
presetVoices.append(text)
|
||||
appStore.saveScenicQueuePresetVoices(presetVoices)
|
||||
appStore.scenicQueue.savePresetVoices(presetVoices)
|
||||
onShowMessage?("已保存到本地")
|
||||
notifyStateChange()
|
||||
}
|
||||
@ -260,7 +260,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
func deletePresetVoice(at index: Int) {
|
||||
guard presetVoices.indices.contains(index) else { return }
|
||||
let removed = presetVoices.remove(at: index)
|
||||
appStore.saveScenicQueuePresetVoices(presetVoices)
|
||||
appStore.scenicQueue.savePresetVoices(presetVoices)
|
||||
if ttsLoopAnchorText == removed {
|
||||
ttsManager.stopCustomTextLoop()
|
||||
ttsLoopAnchorText = nil
|
||||
@ -332,11 +332,11 @@ final class ScenicQueueSettingsViewModel {
|
||||
)
|
||||
do {
|
||||
try await api.saveSetting(request)
|
||||
appStore.scenicQueuePunchSpotId = String(ids.scenicSpotId)
|
||||
appStore.scenicQueuePunchSpotName = selectedPunchSpotLabel
|
||||
appStore.scenicQueueRemark = remark
|
||||
appStore.saveScenicQueuePresetVoices(presetVoices)
|
||||
appStore.saveScenicQueueSettingsSnapshot(currentSnapshot())
|
||||
appStore.scenicQueue.punchSpotId = String(ids.scenicSpotId)
|
||||
appStore.scenicQueue.punchSpotName = selectedPunchSpotLabel
|
||||
appStore.scenicQueue.remark = remark
|
||||
appStore.scenicQueue.savePresetVoices(presetVoices)
|
||||
appStore.scenicQueue.saveSettingsSnapshot(currentSnapshot())
|
||||
_ = try? await api.stats(scenicId: ids.scenicId, scenicSpotId: ids.scenicSpotId)
|
||||
onShowMessage?("保存成功")
|
||||
onNavigateBack?()
|
||||
@ -346,7 +346,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
}
|
||||
|
||||
private func fetchQueueSettingAfterPunchSpotSelected(_ spotId: String, api: ScenicQueueAPIProtocol) async {
|
||||
guard let scenicId = Int64("\(appStore.currentScenicId)"),
|
||||
guard let scenicId = Int64("\(appStore.session.currentScenicId)"),
|
||||
let scenicSpotId = Int64(spotId),
|
||||
scenicId > 0,
|
||||
scenicSpotId > 0 else { return }
|
||||
@ -480,7 +480,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
}
|
||||
|
||||
private func currentScenicAndSpotIdsForSelected() -> (scenicId: Int64, scenicSpotId: Int64)? {
|
||||
guard let scenicId = Int64("\(appStore.currentScenicId)"), scenicId > 0,
|
||||
guard let scenicId = Int64("\(appStore.session.currentScenicId)"), scenicId > 0,
|
||||
let spotIdText = selectedPunchSpotId?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
let scenicSpotId = Int64(spotIdText), scenicSpotId > 0 else { return nil }
|
||||
return (scenicId, scenicSpotId)
|
||||
|
||||
@ -97,8 +97,8 @@ final class ScenicQueueViewModel {
|
||||
/// 开始 WebSocket 订阅。
|
||||
func startQueueStatsPolling(api: ScenicQueueAPIProtocol) {
|
||||
guard queueGatePassed,
|
||||
let scenicId = Int64("\(appStore.currentScenicId)"),
|
||||
let scenicSpotId = Int64(appStore.scenicQueuePunchSpotId),
|
||||
let scenicId = Int64("\(appStore.session.currentScenicId)"),
|
||||
let scenicSpotId = Int64(appStore.scenicQueue.punchSpotId),
|
||||
scenicId > 0,
|
||||
scenicSpotId > 0 else { return }
|
||||
Task {
|
||||
@ -186,16 +186,16 @@ final class ScenicQueueViewModel {
|
||||
/// 确认用户标记。
|
||||
func confirmUserMark(api: ScenicQueueAPIProtocol, markAsFreelancePhotog: Bool, queueBanDays: Int) async {
|
||||
guard let pendingMark else { return }
|
||||
guard appStore.currentScenicId > 0 else {
|
||||
guard appStore.session.currentScenicId > 0 else {
|
||||
onShowMessage?("请先选择景区")
|
||||
return
|
||||
}
|
||||
let request = ScenicQueueUserMarkRequest(
|
||||
uid: pendingMark.uid,
|
||||
scenicId: Int64(appStore.currentScenicId),
|
||||
scenicId: Int64(appStore.session.currentScenicId),
|
||||
markAsFreelancePhotog: markAsFreelancePhotog ? 1 : 0,
|
||||
queueBanDays: markAsFreelancePhotog ? min(max(queueBanDays, 0), 999) : nil,
|
||||
operatorId: Int(appStore.userId)
|
||||
operatorId: Int(appStore.session.userId)
|
||||
)
|
||||
do {
|
||||
try await api.userMark(request)
|
||||
@ -270,7 +270,7 @@ final class ScenicQueueViewModel {
|
||||
func confirmRequeueTicket(api: ScenicQueueAPIProtocol) async {
|
||||
guard let pending = pendingRequeue else { return }
|
||||
dismissRequeueDialog()
|
||||
guard let operatorId = Int(appStore.userId), operatorId > 0 else {
|
||||
guard let operatorId = Int(appStore.session.userId), operatorId > 0 else {
|
||||
onShowMessage?("请先登录")
|
||||
return
|
||||
}
|
||||
@ -395,18 +395,18 @@ final class ScenicQueueViewModel {
|
||||
}
|
||||
|
||||
private func refreshSelectedPunchSpotLine() {
|
||||
selectedPunchSpotLine = appStore.hasScenicQueuePunchSpotSaved()
|
||||
? appStore.scenicQueuePunchSpotName.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "--"
|
||||
selectedPunchSpotLine = appStore.scenicQueue.hasPunchSpotSaved()
|
||||
? appStore.scenicQueue.punchSpotName.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "--"
|
||||
: "--"
|
||||
refreshShootingCallConfig()
|
||||
}
|
||||
|
||||
private func refreshQueueGateLocally() {
|
||||
queueGatePassed = appStore.hasScenicQueuePunchSpotSaved()
|
||||
queueGatePassed = appStore.scenicQueue.hasPunchSpotSaved()
|
||||
}
|
||||
|
||||
private func refreshShootingCallConfig() {
|
||||
let snapshot = appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
let snapshot = appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
showStartShootingButton = snapshot.showStartShootingButton
|
||||
quickCallButtonEnabled = snapshot.quickCallButtonEnabled
|
||||
prepareCallButtonEnabled = snapshot.prepareCallButtonEnabled
|
||||
@ -419,18 +419,18 @@ final class ScenicQueueViewModel {
|
||||
guard data.exists, let setting = data.setting,
|
||||
setting.scenicId == nil || setting.scenicId == ids.scenicId,
|
||||
setting.scenicSpotId == nil || setting.scenicSpotId == ids.scenicSpotId else { return }
|
||||
let local = appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
appStore.saveScenicQueueSettingsSnapshot(setting.toSnapshot(fallback: local))
|
||||
appStore.scenicQueueRemark = setting.remark ?? ""
|
||||
let local = appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
appStore.scenicQueue.saveSettingsSnapshot(setting.toSnapshot(fallback: local))
|
||||
appStore.scenicQueue.remark = setting.remark ?? ""
|
||||
if let voices = setting.voiceBroadcasts {
|
||||
appStore.saveScenicQueuePresetVoices(
|
||||
appStore.scenicQueue.savePresetVoices(
|
||||
voices
|
||||
.sorted { ($0.sortOrder ?? Int.max) < ($1.sortOrder ?? Int.max) }
|
||||
.compactMap { $0.content?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty }
|
||||
)
|
||||
}
|
||||
if let name = setting.scenicSpotName?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty {
|
||||
appStore.scenicQueuePunchSpotName = name
|
||||
appStore.scenicQueue.punchSpotName = name
|
||||
selectedPunchSpotLine = name
|
||||
}
|
||||
refreshShootingCallConfig()
|
||||
@ -501,7 +501,7 @@ final class ScenicQueueViewModel {
|
||||
private func handleSocketMessage(_ message: ScenicQueueSocketMessage) {
|
||||
guard let params = message.data?.params,
|
||||
let changedSpotId = params.scenicSpotId,
|
||||
let currentSpotId = Int64(appStore.scenicQueuePunchSpotId),
|
||||
let currentSpotId = Int64(appStore.scenicQueue.punchSpotId),
|
||||
changedSpotId == currentSpotId else { return }
|
||||
switch message.data?.action {
|
||||
case ScenicQueueSocketAction.queueUpdated.rawValue:
|
||||
@ -516,7 +516,7 @@ final class ScenicQueueViewModel {
|
||||
|
||||
private func handleRemoteTicketCalled(_ params: ScenicQueueSocketParams) {
|
||||
guard let recordId = params.recordId, recordId > 0 else { return }
|
||||
if let myUid = Int64(appStore.userId), let operatorUid = params.operatorUid, myUid == operatorUid {
|
||||
if let myUid = Int64(appStore.session.userId), let operatorUid = params.operatorUid, myUid == operatorUid {
|
||||
return
|
||||
}
|
||||
let eventId = params.eventId?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
@ -614,7 +614,7 @@ final class ScenicQueueViewModel {
|
||||
}
|
||||
|
||||
private func resolveShootingTiming() -> (totalSeconds: Int, intervalSeconds: Int, readableThresholdSeconds: Int) {
|
||||
let snapshot = appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
let snapshot = appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
let total = max(0, snapshot.shootMinute * 60 + snapshot.shootSecond)
|
||||
let interval = (40...60).contains(snapshot.broadcastIntervalSec) ? snapshot.broadcastIntervalSec : 50
|
||||
let readable = (10...30).contains(snapshot.countdownThresholdSec) ? snapshot.countdownThresholdSec : 15
|
||||
@ -627,7 +627,7 @@ final class ScenicQueueViewModel {
|
||||
}
|
||||
|
||||
private func speakFollowingTickets(after queueNo: String) async {
|
||||
let count = (appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()).autoCallAheadCount.clamped(to: 0...5)
|
||||
let count = (appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()).autoCallAheadCount.clamped(to: 0...5)
|
||||
guard count > 0, let index = currentQueue.firstIndex(where: { $0.queueNo == queueNo }) else { return }
|
||||
let nextNos = currentQueue.dropFirst(index + 1).prefix(count).map(\.queueNo).filter { !$0.isEmpty }
|
||||
guard !nextNos.isEmpty else { return }
|
||||
@ -639,9 +639,9 @@ final class ScenicQueueViewModel {
|
||||
}
|
||||
|
||||
private func currentScenicAndSpotIds() -> (scenicId: Int64, scenicSpotId: Int64)? {
|
||||
let scenicId = Int64(appStore.currentScenicId)
|
||||
let scenicId = Int64(appStore.session.currentScenicId)
|
||||
guard scenicId > 0,
|
||||
let scenicSpotId = Int64(appStore.scenicQueuePunchSpotId),
|
||||
let scenicSpotId = Int64(appStore.scenicQueue.punchSpotId),
|
||||
scenicSpotId > 0 else { return nil }
|
||||
return (scenicId, scenicSpotId)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user