Advance UIKit rewrite with AMap integration and core UI modules.
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>
This commit is contained in:
@ -9,6 +9,7 @@ import Foundation
|
||||
|
||||
/// 排队语音播报内容。
|
||||
struct ScenicQueueAnnouncement: Equatable {
|
||||
/// 枚举,定义相关常量或状态。
|
||||
enum Kind: Equatable {
|
||||
case newTickets(count: Int)
|
||||
case calledTicket(id: Int64)
|
||||
|
||||
@ -69,6 +69,7 @@ final class ScenicQueueRuntime {
|
||||
self.scenePhase = scenePhase
|
||||
|
||||
guard !suspendedByQueueScreen else {
|
||||
// 排队页自行监听时,避免双通道轮询
|
||||
stop()
|
||||
return
|
||||
}
|
||||
@ -86,6 +87,7 @@ final class ScenicQueueRuntime {
|
||||
return
|
||||
}
|
||||
|
||||
// 景区或打卡点切换时重置播报状态,避免跨点误报
|
||||
if lastScenicId != scenicId || lastSpotId != spotId {
|
||||
resetSnapshot()
|
||||
lastScenicId = scenicId
|
||||
@ -93,6 +95,7 @@ final class ScenicQueueRuntime {
|
||||
}
|
||||
|
||||
if scenePhase == .background, !backgroundPollingEnabled {
|
||||
// 用户关闭后台轮询时,进入后台即停止
|
||||
stop()
|
||||
return
|
||||
}
|
||||
@ -126,6 +129,7 @@ final class ScenicQueueRuntime {
|
||||
Task { await pollOnce() }
|
||||
}
|
||||
|
||||
/// 启动IfNeeded流程。
|
||||
private func startIfNeeded() {
|
||||
guard pollTask == nil else { return }
|
||||
isMonitoring = true
|
||||
@ -137,6 +141,7 @@ final class ScenicQueueRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
/// 执行Loop循环或任务。
|
||||
private func runLoop() async {
|
||||
while !Task.isCancelled {
|
||||
await pollOnce()
|
||||
@ -145,6 +150,7 @@ final class ScenicQueueRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
/// 轮询Once数据。
|
||||
private func pollOnce() async {
|
||||
guard let api, let scenicId else { return }
|
||||
let spotId = selectedSpotId()
|
||||
@ -168,6 +174,7 @@ final class ScenicQueueRuntime {
|
||||
page: 1,
|
||||
pageSize: 20
|
||||
)
|
||||
// 并行拉取统计与排队列表,减少单次轮询耗时
|
||||
let (stats, home) = try await (statsData, homeData)
|
||||
handle(stats: stats, tickets: home.list?.list ?? [])
|
||||
lastError = nil
|
||||
@ -176,6 +183,7 @@ final class ScenicQueueRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理相关事件。
|
||||
private func handle(stats: ScenicQueueStatsData, tickets: [ScenicQueueTicket]) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "HH:mm:ss"
|
||||
@ -190,6 +198,7 @@ final class ScenicQueueRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
/// 轮询IntervalSeconds数据。
|
||||
private func pollIntervalSeconds() -> UInt64 {
|
||||
switch scenePhase {
|
||||
case .active:
|
||||
@ -203,11 +212,13 @@ final class ScenicQueueRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
/// 读取用户当前选中的打卡点 ID。
|
||||
private func selectedSpotId() -> Int {
|
||||
ScenicQueueSettingsStore.selectedSpotId(userId: userId, scenicId: scenicId)
|
||||
?? UserDefaults.standard.integer(forKey: ScenicQueueLocalSettings.selectedSpotIdKey)
|
||||
}
|
||||
|
||||
/// 重置Snapshot状态。
|
||||
private func resetSnapshot() {
|
||||
announcementState.reset()
|
||||
lastQueueCount = 0
|
||||
@ -215,6 +226,7 @@ final class ScenicQueueRuntime {
|
||||
lastSpokenText = ""
|
||||
}
|
||||
|
||||
/// 申请后台任务,保证进入后台时仍可短时轮询。
|
||||
private func beginBackgroundTask() {
|
||||
guard backgroundTaskId == .invalid else { return }
|
||||
backgroundTaskId = UIApplication.shared.beginBackgroundTask(withName: "scenic.queue.poll") { [weak self] in
|
||||
@ -225,6 +237,7 @@ final class ScenicQueueRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
/// 结束后台任务。
|
||||
private func endBackgroundTask() {
|
||||
guard backgroundTaskId != .invalid else { return }
|
||||
UIApplication.shared.endBackgroundTask(backgroundTaskId)
|
||||
@ -238,6 +251,7 @@ final class ScenicQueueSpeechService: NSObject, AVSpeechSynthesizerDelegate {
|
||||
private let synthesizer = AVSpeechSynthesizer()
|
||||
private var pendingContinuations: [CheckedContinuation<Void, Never>] = []
|
||||
|
||||
/// 初始化实例。
|
||||
override init() {
|
||||
super.init()
|
||||
synthesizer.delegate = self
|
||||
@ -265,6 +279,7 @@ final class ScenicQueueSpeechService: NSObject, AVSpeechSynthesizerDelegate {
|
||||
resumePending()
|
||||
}
|
||||
|
||||
/// 入队等待处理。
|
||||
private func enqueue(_ normalized: String, continuation: CheckedContinuation<Void, Never>?, replacePending: Bool) {
|
||||
if replacePending {
|
||||
synthesizer.stopSpeaking(at: .immediate)
|
||||
@ -279,14 +294,17 @@ final class ScenicQueueSpeechService: NSObject, AVSpeechSynthesizerDelegate {
|
||||
synthesizer.speak(utterance)
|
||||
}
|
||||
|
||||
/// speechSynthesizer相关逻辑。
|
||||
nonisolated func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
|
||||
Task { @MainActor in self.resumePending() }
|
||||
}
|
||||
|
||||
/// speechSynthesizer相关逻辑。
|
||||
nonisolated func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
|
||||
Task { @MainActor in self.resumePending() }
|
||||
}
|
||||
|
||||
/// 恢复Pending流程。
|
||||
private func resumePending() {
|
||||
let continuations = pendingContinuations
|
||||
pendingContinuations.removeAll()
|
||||
|
||||
@ -74,6 +74,7 @@ final class ScenicQueueSocketClient {
|
||||
webSocketTask = nil
|
||||
}
|
||||
|
||||
/// 持续接收 WebSocket 消息并分发处理。
|
||||
private func receiveLoop(
|
||||
task: URLSessionWebSocketTask,
|
||||
onMessage: @escaping @MainActor (ScenicQueueSocketMessage) -> Void
|
||||
@ -130,6 +131,7 @@ final class ScenicQueueSocketClient {
|
||||
return ScenicQueueSocketMessage(code: code, data: socketData)
|
||||
}
|
||||
|
||||
/// 安全地将任意值转为字符串。
|
||||
private static func stringValue(_ value: Any?) -> String {
|
||||
switch value {
|
||||
case let value as String:
|
||||
@ -143,6 +145,7 @@ final class ScenicQueueSocketClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// int值相关逻辑。
|
||||
private static func intValue(_ value: Any?) -> Int? {
|
||||
if let value = value as? Int { return value }
|
||||
if let value = value as? NSNumber { return value.intValue }
|
||||
@ -150,6 +153,7 @@ final class ScenicQueueSocketClient {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// int64值相关逻辑。
|
||||
private static func int64Value(_ value: Any?) -> Int64? {
|
||||
if let value = value as? Int64 { return value }
|
||||
if let value = value as? Int { return Int64(value) }
|
||||
|
||||
Reference in New Issue
Block a user