Files
location-spoofer/App/LocationActionCoordinator.swift
T

127 lines
4.0 KiB
Swift

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