Files
location-spoofer/App/LocationActionCoordinator.swift

131 lines
4.4 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", "坐标转换", "设置虚拟定位坐标", details: [
"WLOC写入标准": CoordinateConverter.MapCoordinateSystem.wgs84.diagnosticName,
"当前地图标准": CoordinateConverter.currentMapCoordinateSystem.diagnosticName,
"目标所在区域": CoordinateConverter.usesGCJ02ServiceArea(lat: wgs.latitude, lon: wgs.longitude) ? "国内转换区域" : "国外非转换区域",
"取值字段": "coordinatePair.wgs84",
"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)
}
}