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:
2026-06-26 15:16:12 +08:00
parent 9edf993432
commit d99a5b1bf8
124 changed files with 5195 additions and 1536 deletions

View File

@ -9,6 +9,7 @@ import Foundation
///
struct ScenicQueueAnnouncement: Equatable {
///
enum Kind: Equatable {
case newTickets(count: Int)
case calledTicket(id: Int64)

View File

@ -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()

View File

@ -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) }