fix: 优化启动引导、坐标处理和日志管理

This commit is contained in:
xweiba
2026-08-06 16:23:09 +08:00
parent 0e8e83fd33
commit e86909cc2f
30 changed files with 2296 additions and 857 deletions
+355 -137
View File
@@ -27,7 +27,7 @@ private struct RealtimeLocationRequestContext {
struct MapHomeView: View {
@ObservedObject var setup: SetupCoordinator
@StateObject private var favorites = FavoriteLocationStore()
@StateObject private var favorites: FavoriteLocationStore
@StateObject private var actions = LocationActionCoordinator()
@ObservedObject private var proxy = ProxyManager.shared
@StateObject private var realtime = RealtimeLocationManager.shared
@@ -39,13 +39,13 @@ struct MapHomeView: View {
@State private var isSearching = false
@State private var searchRequestID: UInt64 = 0
@State private var searchError = ""
@State private var mapDidInitialize = false
@State private var mapRuntimeDidStart = false
@State private var activeSheet: HomeSheet?
@State private var showEnableTip = false
@State private var showDisableTip = false
@State private var activeTip: TipKind?
@State private var manualHint = ""
@AppStorage("activationTipDisabled") private var activationTipDisabled = false
private let tipPreferences = VirtualLocationTipPreferences()
@State private var editingFavorite: FavoriteLocation?
@State private var editName = ""
@State private var reverseGeocodeTask: Task<Void, Never>?
@@ -56,8 +56,6 @@ struct MapHomeView: View {
@State private var wifiChangeObserverToken: UUID?
@State private var wifiVerificationTask: Task<Void, Never>?
@State private var wifiVerificationID: UUID?
@State private var tileProbeTask: Task<Void, Never>?
@State private var tileProbeID: UUID?
@State private var copyConfirmed = false
@State private var spoofState: SpoofState = .idle
@State private var locationOperationTask: Task<Void, Never>?
@@ -68,29 +66,67 @@ struct MapHomeView: View {
init(setup: SetupCoordinator) {
self.setup = setup
let favoriteStore = FavoriteLocationStore()
_favorites = StateObject(wrappedValue: favoriteStore)
let savedCoord = LastCoordinateStore.load()
let initialZoom = ViewportStore.loadOrDefault()
let initialZoom = savedCoord?.zoomMeters ?? ViewportStore.loadOrDefault()
let selectedFavorite = favoriteStore.selectedFavorite
let initialCoord: CLLocationCoordinate2D
let initialSource: MapSelectionSource
let initialName: String?
if let saved = savedCoord {
let display = CoordinateConverter.toDisplay(lat: saved.coordinate.latitude, lon: saved.coordinate.longitude)
initialCoord = CLLocationCoordinate2D(latitude: display.lat, longitude: display.lon)
initialCoord = saved.coordinate(for: CoordinateConverter.currentMapCoordinateSystem)
if let selectedFavorite,
selectedFavorite.coordinatePair.matchesWGS84(
latitude: saved.coordinatePair.wgs84.latitude,
longitude: saved.coordinatePair.wgs84.longitude
) {
initialSource = .favorite(selectedFavorite.id)
initialName = selectedFavorite.name
} else {
initialSource = .initial
initialName = nil
}
} else if let selectedFavorite {
initialCoord = selectedFavorite.coordinatePair.coordinate(for: CoordinateConverter.currentMapCoordinateSystem)
initialSource = .favorite(selectedFavorite.id)
initialName = selectedFavorite.name
} else {
initialCoord = CLLocationCoordinate2D(latitude: 22.544577, longitude: 113.94114)
// The fallback location is defined as WGS-84 and rendered in the
// coordinate system resolved by the startup gate.
initialCoord = CoordinateConverter.coordinatePair(
lat: 22.544577,
lon: 113.94114,
mapCoordinateSystem: .wgs84
).coordinate(for: CoordinateConverter.currentMapCoordinateSystem)
initialSource = .initial
initialName = nil
}
RuntimeLogger.info("APP", "地图", "初始化", details: [
"zoom": String(initialZoom),
"有缓存": String(savedCoord != nil)
"有缓存": String(savedCoord != nil),
"初始来源": String(describing: initialSource),
"地图标准": CoordinateConverter.currentMapCoordinateSystem.rawValue
])
_mapState = StateObject(wrappedValue: MapLocationState(
initialCoordinate: initialCoord,
initialViewportMeters: initialZoom
initialViewportMeters: initialZoom,
initialSource: initialSource,
initialName: initialName
))
if let settings = WlocSettingsStore.load(), settings.enabled {
_spoofState = State(initialValue: .active)
_activeSpoofLat = State(initialValue: settings.latitude)
_activeSpoofLon = State(initialValue: settings.longitude)
}
}
var body: some View {
ZStack {
MapViewRepresentable(
selection: mapState.selection,
initialViewportMeters: mapState.viewportMeters,
cameraCommand: mapState.cameraCommand,
onRealtimeLocationChanged: { location in
handleNativeRealtimeLocation(location)
@@ -100,10 +136,16 @@ struct MapHomeView: View {
let previousRevision = mapState.selection.revision
let revision = mapState.selectUserMapCenter(coordinate)
guard revision != previousRevision else { return }
let wgs = CoordinateConverter.toStored(lat: coordinate.latitude, lon: coordinate.longitude)
LastCoordinateStore.save(lat: wgs.lat, lon: wgs.lon)
let pair = CoordinatePair(
mapCoordinate: coordinate,
mapCoordinateSystem: CoordinateConverter.currentMapCoordinateSystem
)
LastCoordinateStore.save(
coordinatePair: pair,
zoomMeters: mapState.viewportMeters
)
favorites.select(nil)
scheduleGeocode(coordinate: coordinate, revision: revision)
scheduleGeocode(coordinate: pair.wgs84.coordinate, revision: revision)
},
onViewportChanged: { distance in
mapState.updateViewport(distanceMeters: distance)
@@ -111,12 +153,19 @@ struct MapHomeView: View {
onMapTap: { coordinate in
favorites.select(nil)
let revision = mapState.selectMapTap(coordinate)
let wgs = CoordinateConverter.toStored(lat: coordinate.latitude, lon: coordinate.longitude)
LastCoordinateStore.save(lat: wgs.lat, lon: wgs.lon)
scheduleGeocode(coordinate: coordinate, revision: revision)
let pair = CoordinatePair(
mapCoordinate: coordinate,
mapCoordinateSystem: CoordinateConverter.currentMapCoordinateSystem
)
LastCoordinateStore.save(
coordinatePair: pair,
zoomMeters: mapState.viewportMeters
)
scheduleGeocode(coordinate: pair.wgs84.coordinate, revision: revision)
},
onUserZoomChanged: { distance in
ViewportStore.save(distance)
LastCoordinateStore.updateZoom(distance)
},
onZoomIn: { mapState.zoom(by: 0.5) },
onZoomOut: { mapState.zoom(by: 2) }
@@ -188,9 +237,9 @@ struct MapHomeView: View {
Button("知道了", role: .cancel) {}
} message: { Text(manualHint) }
.onAppear {
initializeMap()
Task { await setup.refreshTrust() }
startTileProbe()
startMapRuntimeOnce()
// ContentView performs the startup environment test before this map
// view is constructed. Do not immediately run it again here.
registerWiFiChangeObserver()
}
.onDisappear {
@@ -201,14 +250,6 @@ struct MapHomeView: View {
wifiVerificationTask?.cancel()
wifiVerificationTask = nil
wifiVerificationID = nil
tileProbeTask?.cancel()
tileProbeTask = nil
tileProbeID = nil
}
.onChange(of: net.isAirplaneMode) { airplane in
if !airplane {
Task { await setup.refreshTrust() }
}
}
.onChange(of: proxy.isRunning) { running in
if !running && spoofState == .active {
@@ -350,7 +391,7 @@ struct MapHomeView: View {
if actions.virtualLocationEnabled {
activeTip = .activation
} else {
showDisableTip = true
activeTip = .deactivation
}
} label: {
Text(actions.virtualLocationEnabled ? "无法生效?" : "无法取消?")
@@ -368,16 +409,14 @@ struct MapHomeView: View {
return
}
let snapshot = currentSelectionFavorite
let wgs = CoordinateConverter.toStored(lat: snapshot.latitude, lon: snapshot.longitude)
let favorite = favorites.save(
name: snapshot.name,
latitude: wgs.lat,
longitude: wgs.lon,
mapCoordinate: mapState.selection.coordinate,
mapCoordinateSystem: CoordinateConverter.currentMapCoordinateSystem,
accuracy: snapshot.accuracy
)
let display = CoordinateConverter.toDisplay(lat: favorite.latitude, lon: favorite.longitude)
mapState.selectFavorite(
CLLocationCoordinate2D(latitude: display.lat, longitude: display.lon),
favorite.coordinatePair.coordinate(for: CoordinateConverter.currentMapCoordinateSystem),
id: favorite.id,
name: favorite.name
)
@@ -445,8 +484,10 @@ struct MapHomeView: View {
guard spoofState == .active,
let sLat = activeSpoofLat,
let sLon = activeSpoofLon else { return false }
return abs(sLat - mapState.selection.coordinate.latitude) > 0.0001
|| abs(sLon - mapState.selection.coordinate.longitude) > 0.0001
return !currentSelectionFavorite.coordinatePair.matchesWGS84(
latitude: sLat,
longitude: sLon
)
}
private var buttonTitle: String {
@@ -485,7 +526,7 @@ struct MapHomeView: View {
spoofState = .verifying
locationOperationTask = Task { @MainActor in
let result = await setup.runVerificationTest(testLat: target.latitude, testLon: target.longitude)
let result = await setup.runVerificationTest()
guard !Task.isCancelled,
operationID == locationOperationID,
selectionRevision == mapState.selection.revision else {
@@ -508,8 +549,13 @@ struct MapHomeView: View {
"applied": String(applied),
"spoofState": String(describing: spoofState)
])
if applied && !activationTipDisabled {
showEnableTip = true
if applied {
let count = tipPreferences.recordSuccessfulOperation(.activation)
RuntimeLogger.info("APP", "提醒", "累计开启虚拟定位次数", details: [
"次数": String(count),
"可显示不再提醒": String(tipPreferences.canSuppress(.activation))
])
showEnableTip = tipPreferences.shouldPresentAutomaticTip(.activation)
}
} else {
spoofState = actions.virtualLocationEnabled ? .active : .idle
@@ -517,7 +563,11 @@ struct MapHomeView: View {
"result": result.id,
"spoofState": String(describing: spoofState)
])
if let tip = result.tipKind {
if result == .certNotTrusted {
RuntimeLogger.warning("APP", "定位", "开启前检测发现证书异常,进入证书安装引导")
activeTip = nil
setup.applyVerificationResult(result)
} else if let tip = result.tipKind {
activeTip = tip
}
}
@@ -533,7 +583,12 @@ struct MapHomeView: View {
spoofState = .idle
activeSpoofLat = nil
activeSpoofLon = nil
showDisableTip = true
let count = tipPreferences.recordSuccessfulOperation(.deactivation)
RuntimeLogger.info("APP", "提醒", "累计关闭虚拟定位次数", details: [
"次数": String(count),
"可显示不再提醒": String(tipPreferences.canSuppress(.deactivation))
])
showDisableTip = tipPreferences.shouldPresentAutomaticTip(.deactivation)
}
@@ -574,69 +629,40 @@ struct MapHomeView: View {
mapState.selection.coordinate.latitude,
mapState.selection.coordinate.longitude
),
latitude: mapState.selection.coordinate.latitude,
longitude: mapState.selection.coordinate.longitude,
coordinatePair: .init(
mapCoordinate: mapState.selection.coordinate,
mapCoordinateSystem: CoordinateConverter.currentMapCoordinateSystem
),
accuracy: 25
)
}
private var testFavorite: FavoriteLocation { currentSelectionFavorite }
private func initializeMap() {
guard !mapDidInitialize else { return }
mapDidInitialize = true
if let selected = favorites.selectedFavorite {
let display = CoordinateConverter.toDisplay(lat: selected.latitude, lon: selected.longitude)
mapState.selectFavorite(
CLLocationCoordinate2D(latitude: display.lat, longitude: display.lon),
id: selected.id,
name: selected.name
private func startMapRuntimeOnce() {
guard !mapRuntimeDidStart else { return }
mapRuntimeDidStart = true
let pair = LastCoordinateStore.load()?.coordinatePair
?? CoordinatePair(
mapCoordinate: mapState.selection.coordinate,
mapCoordinateSystem: CoordinateConverter.currentMapCoordinateSystem
)
LastCoordinateStore.save(lat: selected.latitude, lon: selected.longitude)
} else {
mapState.focusSelection(distanceMeters: mapState.viewportMeters)
scheduleGeocode(
coordinate: mapState.selection.coordinate,
revision: mapState.selection.revision
)
}
// > > init
let savedCoord = LastCoordinateStore.load()
if savedCoord == nil {
requestRealtimeLocation()
}
}
private func startTileProbe(force: Bool = false) {
guard tileProbeTask == nil else { return }
let probeID = UUID()
tileProbeID = probeID
tileProbeTask = Task { @MainActor in
defer {
if tileProbeID == probeID {
tileProbeTask = nil
tileProbeID = nil
}
}
guard let change = await CoordinateConverter.detectTileByFixedGeocode(force: force),
!Task.isCancelled else { return }
reprojectMapSelection(for: change)
}
}
private func reprojectMapSelection(for change: CoordinateConverter.TileTypeChange) {
// CLLocationManager samples are WGS-84 already. Other map interactions
// are display coordinates and must be converted through WGS-84 once.
guard mapState.selection.source != .realtime else { return }
let coordinate = CoordinateConverter.reprojectDisplayCoordinate(
mapState.selection.coordinate,
from: change.previous,
to: change.current
scheduleGeocode(
coordinate: pair.wgs84.coordinate,
revision: mapState.selection.revision
)
mapState.reprojectSelectionForTileChange(coordinate)
RuntimeLogger.info("APP", "坐标转换", "瓦片类型切换后已重投影当前选点", details: [
}
private func reprojectMapSelection(for change: CoordinateConverter.MapCoordinateSystemChange) {
// Every current selection is persisted as a complete coordinate pair at
// its input boundary. Replaying the matching stored representation
// avoids a second GCJ/WGS conversion and its accumulated offset.
guard let stored = LastCoordinateStore.load() else {
RuntimeLogger.warning("APP", "坐标转换", "地图坐标标准切换时未找到当前选点缓存")
return
}
mapState.reprojectSelectionForMapCoordinateSystemChange(stored.coordinate(for: change.current))
RuntimeLogger.info("APP", "坐标转换", "地图坐标标准切换后已使用缓存坐标对回显当前选点", details: [
"from": change.previous.rawValue,
"to": change.current.rawValue
])
@@ -644,16 +670,20 @@ struct MapHomeView: View {
private func registerWiFiChangeObserver() {
guard wifiChangeObserverToken == nil else { return }
wifiChangeObserverToken = net.observeWiFiChanges { [self] in
handleWiFiChange()
wifiChangeObserverToken = net.observeWiFiChanges { [self] reason in
handleWiFiChange(reason: reason)
}
}
private func handleWiFiChange() {
private func handleWiFiChange(reason: WiFiChangeReason) {
RuntimeLogger.info("APP", "WiFi", "检测到 Wi-Fi 网络变化", details: [
"原因": reason.rawValue,
"虚拟定位已开启": String(spoofState == .active)
])
guard spoofState == .active else { return }
if wifiVerificationTask != nil {
RuntimeLogger.info("APP", "WiFi", "网络仍在变化,重新计算环境检测等待时间")
}
wifiVerificationTask?.cancel()
let verificationID = UUID()
wifiVerificationID = verificationID
@@ -664,34 +694,94 @@ struct MapHomeView: View {
wifiVerificationID = nil
}
}
let stabilizationNanoseconds: UInt64 = 5_000_000_000
RuntimeLogger.info("APP", "WiFi", "等待 Wi-Fi 连接稳定后检测", details: [
"等待秒数": "5",
"事件原因": reason.rawValue
])
do {
try await Task.sleep(nanoseconds: 3_000_000_000)
try await Task.sleep(nanoseconds: stabilizationNanoseconds)
} catch {
RuntimeLogger.debug("APP", "WiFi", "延时检测已被更新的网络事件取消")
return
}
guard !Task.isCancelled, spoofState == .active else { return }
RuntimeLogger.info("APP", "WiFi", "开始重新验证")
let target = currentSelectionFavorite
let result = await setup.runVerificationTest(testLat: target.latitude, testLon: target.longitude)
guard !Task.isCancelled, spoofState == .active else { return }
RuntimeLogger.info("APP", "WiFi", "重新验证完成", details: [
"success": String(result.isSuccess),
"tipKind": result.tipKind?.rawValue ?? "nil"
])
if !result.isSuccess, let tip = result.tipKind {
activeTip = tip
guard net.isSatisfied, net.isWiFiEnabled else {
RuntimeLogger.warning("APP", "WiFi", "稳定等待结束后仍未连接 Wi-Fi,提示检查代理", details: [
"网络可用": String(net.isSatisfied),
"Wi-Fi接口": String(net.isWiFiEnabled)
])
activeTip = .proxySetup
return
}
// A manual diagnostics request can occupy the verifier for its full
// eight-second URL timeout. Wait long enough to run this check after
// it finishes instead of silently dropping the Wi-Fi-change check.
let maximumAttempts = 11
for attempt in 1...maximumAttempts {
RuntimeLogger.info("APP", "WiFi", "开始后台环境检测", details: [
"尝试": "\(attempt)/\(maximumAttempts)",
"SSID可读取": String(net.currentSSID != nil)
])
let result = await setup.runVerificationTest()
guard !Task.isCancelled, spoofState == .active else { return }
if result == .verificationInProgress, attempt < maximumAttempts {
RuntimeLogger.info("APP", "WiFi", "已有环境检测运行,1 秒后重试", details: [
"尝试": "\(attempt)/\(maximumAttempts)"
])
do {
try await Task.sleep(nanoseconds: 1_000_000_000)
} catch {
return
}
continue
}
if result == .certNotTrusted {
RuntimeLogger.warning("APP", "WiFi", "后台环境检测发现证书异常,进入证书安装引导")
activeTip = nil
setup.applyVerificationResult(result)
return
}
let tip = result.wifiChangeReminderTipKind
RuntimeLogger.info("APP", "WiFi", "后台环境检测完成", details: [
"结果": result.id,
"success": String(result.isSuccess),
"提示": tip?.rawValue ?? ""
])
if !result.isSuccess, let tip {
activeTip = tip
} else if result == .verificationInProgress {
RuntimeLogger.warning("APP", "WiFi", "环境检测连续被占用,本次不重复弹窗")
}
return
}
}
}
private func requestRealtimeLocation() {
let intent = mapState.beginRealtimeIntent()
startTileProbe()
RuntimeLogger.info("APP", "实时定位", "用户点击实时定位", details: [
"intentID": String(intent.id),
"选点revision": String(intent.selectionRevision),
"当前地图标准": CoordinateConverter.currentMapCoordinateSystem.rawValue,
"蓝点缓存存在": String(mapState.realtimeLocation != nil),
"CLLocationManager请求中": String(realtime.isRequesting)
])
// MKMapView userLocation
if let loc = mapState.realtimeLocation {
RealtimeLocationTrace.log("实时定位按钮直接使用 MapKit 蓝点缓存", location: loc, details: [
"intentID": String(intent.id),
"来源": "MapLocationState.realtimeLocation"
])
acceptRealtimeLocation(loc.coordinate, intent: intent, source: "MapKit蓝点")
return
}
RuntimeLogger.info("APP", "实时定位", "MapKit 蓝点尚不可用,启动 CLLocationManager 兜底", details: [
"intentID": String(intent.id)
])
startRealtimeLocationRequest(
source: "CLLocationManager",
showFailureAlert: true,
@@ -700,14 +790,25 @@ struct MapHomeView: View {
}
private func handleNativeRealtimeLocation(_ location: CLLocation) {
RealtimeLocationTrace.log("主页收到 MapKit 蓝点回调", location: location, details: [
"存在待处理请求": String(realtimeRequestContext != nil),
"当前选点revision": String(mapState.selection.revision)
])
mapState.updateRealtimeLocation(location)
guard let context = realtimeRequestContext else { return }
guard let context = realtimeRequestContext else {
RuntimeLogger.debug("APP", "实时定位", "蓝点已缓存;当前没有待处理的实时定位意图")
return
}
// MKMapView's MKUserLocation is the visible blue point. When it arrives,
// fulfill the pending intent from that exact sample and cancel the slower
// CLLocationManager fallback so the camera and dot cannot disagree.
realtimeRequestContext = nil
realtimeRequestTask?.cancel()
RuntimeLogger.info("APP", "实时定位", "MapKit 蓝点抢先完成请求,取消 CLLocationManager 兜底", details: [
"intentID": String(context.intent.id),
"原兜底来源": context.source
])
acceptRealtimeLocation(location.coordinate, intent: context.intent, source: "蓝点(途中)→\(context.source)")
}
@@ -722,16 +823,34 @@ struct MapHomeView: View {
source: source,
showFailureAlert: showFailureAlert
)
RuntimeLogger.info("APP", "实时定位", "登记实时定位请求上下文", details: [
"intentID": String(intent.id),
"选点revision": String(intent.selectionRevision),
"来源": source,
"失败时提示": String(showFailureAlert),
"任务已存在": String(realtimeRequestTask != nil),
"manager请求中": String(realtime.isRequesting)
])
// A button tap during the startup request retargets that same in-flight
// Core Location request to the newer intent instead of being ignored.
guard realtimeRequestTask == nil, !realtime.isRequesting else { return }
guard realtimeRequestTask == nil, !realtime.isRequesting else {
RuntimeLogger.info("APP", "实时定位", "复用进行中的 CLLocationManager 请求并更新意图上下文", details: [
"intentID": String(intent.id)
])
return
}
realtimeRequestTask = Task { @MainActor in
defer {
realtimeRequestTask = nil
realtimeRequestContext = nil
}
guard let coordinate = await realtime.requestLocation() else {
RuntimeLogger.warning("APP", "实时定位", "CLLocationManager 兜底未返回坐标", details: [
"授权状态rawValue": String(realtime.authorizationStatus.rawValue),
"任务已取消": String(Task.isCancelled),
"上下文存在": String(realtimeRequestContext != nil)
])
guard let context = realtimeRequestContext,
context.showFailureAlert,
!Task.isCancelled,
@@ -740,7 +859,16 @@ struct MapHomeView: View {
showLocationAlert = true
return
}
guard !Task.isCancelled, let context = realtimeRequestContext else { return }
RealtimeLocationTrace.coordinate("主页收到 CLLocationManager 兜底坐标", coordinate: coordinate, details: [
"任务已取消": String(Task.isCancelled)
])
guard !Task.isCancelled, let context = realtimeRequestContext else {
RuntimeLogger.info("APP", "实时定位", "丢弃 CLLocationManager 结果:任务已取消或蓝点已抢先完成", details: [
"任务已取消": String(Task.isCancelled),
"上下文存在": String(realtimeRequestContext != nil)
])
return
}
acceptRealtimeLocation(coordinate, intent: context.intent, source: context.source)
}
}
@@ -751,15 +879,48 @@ struct MapHomeView: View {
source: String
) {
let currentViewport = mapState.viewportMeters
let accepted = mapState.acceptRealtimeLocation(coordinate, intent: intent)
RuntimeLogger.info("APP", "地图", "\(source)返回", details: [
"accepted": String(accepted)
let previousMapCoordinateSystem = CoordinateConverter.currentMapCoordinateSystem
let usesDomesticStandard = CoordinateConverter.usesGCJ02ServiceArea(
lat: coordinate.latitude,
lon: coordinate.longitude
)
let mapCoordinateSystemChange = CoordinateConverter.correctMapCoordinateSystemUsingRealtime(coordinate)
if let change = mapCoordinateSystemChange {
reprojectMapSelection(for: change)
}
let pair = CoordinateConverter.coordinatePair(
lat: coordinate.latitude,
lon: coordinate.longitude,
mapCoordinateSystem: .wgs84
)
let accepted = mapState.acceptRealtimeLocation(
pair.coordinate(for: CoordinateConverter.currentMapCoordinateSystem),
intent: intent
)
RuntimeLogger.info("APP", "实时定位", "实时定位坐标完成标准判断并提交到地图", details: [
"来源": source,
"intentID": String(intent.id),
"intent选点revision": String(intent.selectionRevision),
"当前选点revision": String(mapState.selection.revision),
"服务区域使用GCJ": String(usesDomesticStandard),
"修正前地图标准": previousMapCoordinateSystem.rawValue,
"修正后地图标准": CoordinateConverter.currentMapCoordinateSystem.rawValue,
"地图标准发生修正": String(mapCoordinateSystemChange != nil),
"accepted": String(accepted),
"显示坐标字段": CoordinateConverter.currentMapCoordinateSystem.rawValue,
"持久化字段": "WGS-84+GCJ-02"
])
RealtimeLocationTrace.coordinate("原始实时定位坐标(按 WGS-84", coordinate: coordinate, details: [
"来源": source
])
RealtimeLocationTrace.coordinate("地图实际显示坐标", coordinate: pair.coordinate(for: CoordinateConverter.currentMapCoordinateSystem), details: [
"地图标准": CoordinateConverter.currentMapCoordinateSystem.rawValue
])
guard accepted else { return }
//
mapState.focusSelection(distanceMeters: currentViewport)
// WGS-84 toStored
LastCoordinateStore.save(lat: coordinate.latitude, lon: coordinate.longitude)
// Core Location is WGS-84; persist both forms once and render the active form.
LastCoordinateStore.save(coordinatePair: pair, zoomMeters: currentViewport)
favorites.select(nil)
scheduleGeocode(coordinate: coordinate, revision: mapState.selection.revision)
}
@@ -898,8 +1059,11 @@ struct MapHomeView: View {
reverseGeocodeTask?.cancel()
favorites.select(nil)
mapState.selectSearchResult(result.coordinate, name: result.name)
let wgs = CoordinateConverter.toStored(lat: result.coordinate.latitude, lon: result.coordinate.longitude)
LastCoordinateStore.save(lat: wgs.lat, lon: wgs.lon)
LastCoordinateStore.save(
mapCoordinate: result.coordinate,
mapCoordinateSystem: CoordinateConverter.currentMapCoordinateSystem,
zoomMeters: mapState.viewportMeters
)
searchText = result.name
searchResults = []
searchError = ""
@@ -913,13 +1077,15 @@ struct MapHomeView: View {
geocodeDebounceTask?.cancel()
reverseGeocodeTask?.cancel()
favorites.select(favorite.id)
let display = CoordinateConverter.toDisplay(lat: favorite.latitude, lon: favorite.longitude)
mapState.selectFavorite(
CLLocationCoordinate2D(latitude: display.lat, longitude: display.lon),
favorite.coordinatePair.coordinate(for: CoordinateConverter.currentMapCoordinateSystem),
id: favorite.id,
name: favorite.name
)
LastCoordinateStore.save(lat: favorite.latitude, lon: favorite.longitude)
LastCoordinateStore.save(
coordinatePair: favorite.coordinatePair,
zoomMeters: mapState.viewportMeters
)
}
private var enableTipSheet: some View {
@@ -931,16 +1097,38 @@ struct MapHomeView: View {
}
.navigationTitle("虚拟定位已开启").navigationBarTitleDisplayMode(.inline)
.safeAreaInset(edge: .bottom) {
VStack(spacing: 8) {
Button { showEnableTip = false } label: {
Text("知道了").font(.body.weight(.medium)).frame(maxWidth: .infinity).padding(.vertical, 12)
}.buttonStyle(.borderedProminent).tint(.blue)
Button(role: .destructive) {
activationTipDisabled = true
showEnableTip = false
} label: {
Label("关闭不再弹出", systemImage: "bell.slash").frame(maxWidth: .infinity)
}.buttonStyle(.bordered)
HStack(spacing: 12) {
if tipPreferences.canSuppress(.activation) {
Button {
tipPreferences.suppress(.activation)
showEnableTip = false
} label: {
Label("不再提醒", systemImage: "bell.slash.fill")
.font(.body.weight(.semibold))
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
}
.buttonStyle(.borderedProminent)
.tint(.orange)
Button { showEnableTip = false } label: {
Text("知道了")
.font(.body.weight(.medium))
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
}
.buttonStyle(.bordered)
.tint(.blue)
} else {
Button { showEnableTip = false } label: {
Text("知道了")
.font(.body.weight(.medium))
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
}
.buttonStyle(.borderedProminent)
.tint(.blue)
}
}.padding(.horizontal, 16).padding(.bottom, 8)
}
}
@@ -956,9 +1144,39 @@ struct MapHomeView: View {
}
.navigationTitle("虚拟定位已关闭").navigationBarTitleDisplayMode(.inline)
.safeAreaInset(edge: .bottom) {
Button { showDisableTip = false } label: {
Text("知道了").font(.body.weight(.medium)).frame(maxWidth: .infinity).padding(.vertical, 12)
}.buttonStyle(.borderedProminent).tint(.blue).padding(.horizontal, 16).padding(.bottom, 8)
HStack(spacing: 12) {
if tipPreferences.canSuppress(.deactivation) {
Button {
tipPreferences.suppress(.deactivation)
showDisableTip = false
} label: {
Label("不再提醒", systemImage: "bell.slash.fill")
.font(.body.weight(.semibold))
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
}
.buttonStyle(.borderedProminent)
.tint(.orange)
Button { showDisableTip = false } label: {
Text("知道了")
.font(.body.weight(.medium))
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
}
.buttonStyle(.bordered)
.tint(.blue)
} else {
Button { showDisableTip = false } label: {
Text("知道了")
.font(.body.weight(.medium))
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
}
.buttonStyle(.borderedProminent)
.tint(.blue)
}
}.padding(.horizontal, 16).padding(.bottom, 8)
}
}
}