Integrate Amap SDK to replace CoreLocation and MapKit for all location features.

Unify GPS, reverse geocoding, and map display behind LocationProvider with privacy-gated bootstrap after login, aligned with Android.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 11:49:22 +08:00
parent d2879ad7e2
commit cfcd3eee6f
29 changed files with 748 additions and 240 deletions

View File

@ -1,98 +0,0 @@
//
// HomeLocationProvider.swift
// suixinkan
//
import CoreLocation
///
struct HomeLocationSnapshot: Sendable {
let latitude: Double
let longitude: Double
let address: String
}
/// CoreLocation
final class HomeLocationProvider: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
private var coordinateContinuation: CheckedContinuation<CLLocationCoordinate2D, Error>?
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
}
///
func requestSnapshot() async throws -> HomeLocationSnapshot {
let status = manager.authorizationStatus
if status == .notDetermined {
manager.requestWhenInUseAuthorization()
try await Task.sleep(nanoseconds: 300_000_000)
}
let resolvedStatus = manager.authorizationStatus
guard resolvedStatus == .authorizedWhenInUse || resolvedStatus == .authorizedAlways else {
throw HomeLocationProviderError.permissionDenied
}
let coordinate = try await requestCoordinate()
let address = await reverseGeocode(latitude: coordinate.latitude, longitude: coordinate.longitude)
return HomeLocationSnapshot(
latitude: coordinate.latitude,
longitude: coordinate.longitude,
address: address
)
}
/// 使
func reverseGeocode(latitude: Double, longitude: Double) async -> String {
let location = CLLocation(latitude: latitude, longitude: longitude)
let geocoder = CLGeocoder()
do {
let placemarks = try await geocoder.reverseGeocodeLocation(location)
return placemarks.first?.formattedAddress ?? ""
} catch {
return ""
}
}
private func requestCoordinate() async throws -> CLLocationCoordinate2D {
try await withCheckedThrowingContinuation { continuation in
self.coordinateContinuation = continuation
manager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
coordinateContinuation?.resume(returning: location.coordinate)
coordinateContinuation = nil
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
coordinateContinuation?.resume(throwing: error)
coordinateContinuation = nil
}
}
///
enum HomeLocationProviderError: LocalizedError {
case permissionDenied
var errorDescription: String? {
switch self {
case .permissionDenied: "需要定位权限才能使用此功能"
}
}
}
private extension CLPlacemark {
var formattedAddress: String {
[subLocality, locality, administrativeArea]
.compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
.joined(separator: "")
}
}

View File

@ -30,7 +30,7 @@ final class HomeViewModel {
let locationReportService: LocationReportService
private let appStore: AppStore
private let commonMenuStore: HomeCommonMenuStore
private let locationProvider: HomeLocationProvider
private let locationProvider: any LocationProviding
private var timeoutDialogTriggered = false
private var dismissedLocationTimeoutDialog = false
@ -39,7 +39,7 @@ final class HomeViewModel {
appStore: AppStore = .shared,
locationStateStore: HomeLocationStateStore = HomeLocationStateStore(),
commonMenuStore: HomeCommonMenuStore = HomeCommonMenuStore(),
locationProvider: HomeLocationProvider = HomeLocationProvider()
locationProvider: any LocationProviding = LocationProvider.shared
) {
self.appStore = appStore
self.locationStateStore = locationStateStore

View File

@ -27,11 +27,11 @@ final class ScenicSelectionViewModel {
var onNavigateApplyStatus: ((String) -> Void)?
private let appStore: AppStore
private let locationProvider: HomeLocationProvider
private let locationProvider: any LocationProviding
private var currentLat: Double?
private var currentLng: Double?
init(appStore: AppStore = .shared, locationProvider: HomeLocationProvider = HomeLocationProvider()) {
init(appStore: AppStore = .shared, locationProvider: any LocationProviding = LocationProvider.shared) {
self.appStore = appStore
self.locationProvider = locationProvider
}
@ -63,7 +63,7 @@ final class ScenicSelectionViewModel {
let address = snapshot.address.trimmingCharacters(in: .whitespacesAndNewlines)
currentLocationText = address.isEmpty ? "最近的景区" : address
loadFromLocal()
} catch HomeLocationProviderError.permissionDenied {
} catch LocationProviderError.permissionDenied {
onShowMessage?("未授予定位权限,无法获取附近景区")
} catch {
onShowMessage?("定位失败: \(error.localizedDescription)")