Extract shared LocationReportService, wire location_report menu routing, and add unit tests for reporting cooldown and history pagination. Co-authored-by: Cursor <cursoragent@cursor.com>
99 lines
3.2 KiB
Swift
99 lines
3.2 KiB
Swift
//
|
||
// 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: "")
|
||
}
|
||
}
|