import Foundation @MainActor protocol LocationActionProxying: AnyObject { var isRunning: Bool { get } func start() async throws func setCoords(lat: Double, lon: Double, enabled: Bool, accuracy: Int) -> UInt64 } extension ProxyManager: LocationActionProxying {} @MainActor protocol LocationActionSettingsStoring: AnyObject { func load() -> WlocSettings? func save(_ settings: WlocSettings) func clear() } @MainActor final class DeviceWlocSettingsStorage: LocationActionSettingsStoring { func load() -> WlocSettings? { WlocSettingsStore.load() } func save(_ settings: WlocSettings) { WlocSettingsStore.save(settings) } func clear() { WlocSettingsStore.clear() } } @MainActor final class LocationActionCoordinator: ObservableObject { @Published private(set) var state: LocationActionState = .idle @Published private(set) var virtualLocationEnabled = false @Published private(set) var message = "" private let proxy: any LocationActionProxying private let settings: any LocationActionSettingsStoring init() { self.proxy = ProxyManager.shared self.settings = DeviceWlocSettingsStorage() self.virtualLocationEnabled = settings.load()?.enabled == true } init(proxy: any LocationActionProxying, settings: any LocationActionSettingsStoring) { self.proxy = proxy self.settings = settings self.virtualLocationEnabled = settings.load()?.enabled == true } func apply(_ favorite: FavoriteLocation) async -> Bool { guard beginApply() else { return false } do { if !proxy.isRunning { try await proxy.start() } guard !Task.isCancelled else { finishCancelledApply() return false } return commit(favorite) } catch { failApply(error) return false } } /// Commits a target after SetupCoordinator has completed verification. /// This method is synchronous on MainActor so selection revision validation /// and the final settings/proxy write cannot be interleaved by a newer map event. func applyVerified(_ favorite: FavoriteLocation) -> Bool { guard proxy.isRunning else { failApply(ProxyError.startFailed) return false } guard beginApply() else { return false } return commit(favorite) } func clear() { guard !state.isBusy else { return } _ = proxy.setCoords(lat: 0, lon: 0, enabled: false, accuracy: 25) settings.clear() state = .idle virtualLocationEnabled = false message = "已恢复真实定位" } private func beginApply() -> Bool { guard !state.isBusy else { return false } state = .applyingLocation message = "启动代理…" return true } private func commit(_ favorite: FavoriteLocation) -> Bool { // WLOC 合约固定使用持久化的 WGS-84 值,不依赖当前地图地图坐标标准。 let wgs = favorite.coordinatePair.wgs84 let value = WlocSettings( longitude: wgs.longitude, latitude: wgs.latitude, accuracy: favorite.accuracy, enabled: true ) settings.save(value) _ = proxy.setCoords( lat: wgs.latitude, lon: wgs.longitude, enabled: true, accuracy: favorite.accuracy ) RuntimeLogger.info("APP", "坐标转换", "已向代理写入 WGS-84 坐标", details: [ "accuracy": String(favorite.accuracy) ]) state = .idle virtualLocationEnabled = true message = "虚拟定位已开启" return true } private func finishCancelledApply() { state = .idle message = "已取消位置更新" } private func failApply(_ error: Error) { virtualLocationEnabled = false message = "启动失败" state = .failed(error.localizedDescription) RuntimeLogger.error("APP", "Location", "apply失败", error: error) } }