115 lines
3.7 KiB
Swift
115 lines
3.7 KiB
Swift
import Foundation
|
|
import CoreLocation
|
|
import SwiftLocation
|
|
|
|
public enum LocationError: LocalizedError {
|
|
|
|
case generic
|
|
case permission
|
|
case reverseGeocode
|
|
|
|
public var errorDescription: String? {
|
|
switch self {
|
|
case .generic: "Location error"
|
|
case .permission: "Location permission error"
|
|
case .reverseGeocode: "Reverse geocode error"
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
public class RxLocationManager {
|
|
|
|
private let generalErrors: [CLError.Code] = [.locationUnknown, .denied, .network, .headingFailure, .rangingUnavailable, .rangingFailure]
|
|
private let geocodingErrors: [CLError.Code] = [.geocodeCanceled, .geocodeFoundNoResult, .geocodeFoundPartialResult]
|
|
|
|
private static let locationManager: Location = {
|
|
let manger = CLLocationManager()
|
|
manger.desiredAccuracy = kCLLocationAccuracyBest
|
|
return Location(locationManager: manger)
|
|
}()
|
|
|
|
private static var eventTask: Task<VehicleEventDto,Error>?
|
|
public private(set) static var lastEvent: VehicleEventDto?
|
|
|
|
private static func checkPermissions() async throws {
|
|
|
|
switch locationManager.authorizationStatus {
|
|
case .authorizedWhenInUse, .authorizedAlways:
|
|
break
|
|
case .notDetermined:
|
|
let status = try await locationManager.requestPermission(.always)
|
|
if [.authorizedWhenInUse, .authorizedAlways].contains(status) {
|
|
return
|
|
} else {
|
|
throw LocationError.permission
|
|
}
|
|
case .denied:
|
|
throw CLError(.denied)
|
|
default:
|
|
throw LocationError.permission
|
|
}
|
|
}
|
|
|
|
private static func requestLocation() async throws -> VehicleEventDto {
|
|
let locationEvent = try await locationManager.requestLocation(timeout: 20)
|
|
|
|
guard let coordinate = locationEvent.location?.coordinate else {
|
|
throw LocationError.generic
|
|
}
|
|
|
|
let event = VehicleEventDto(lat: coordinate.latitude, lon: coordinate.longitude)
|
|
self.lastEvent = event
|
|
return event
|
|
}
|
|
|
|
@discardableResult
|
|
public static func requestCurrentLocation() async throws -> VehicleEventDto {
|
|
|
|
if let eventTask {
|
|
return try await eventTask.value
|
|
} else {
|
|
try await checkPermissions()
|
|
let task = Task {
|
|
let location = try await requestLocation()
|
|
eventTask = nil
|
|
return location
|
|
}
|
|
eventTask = task
|
|
return try await task.value
|
|
}
|
|
}
|
|
|
|
public static func locationRequestInProgress() -> Bool {
|
|
return self.eventTask != nil
|
|
}
|
|
|
|
public static func getAddressForLocation(latitude: Double, longitude: Double) async throws -> String {
|
|
|
|
try await withCheckedThrowingContinuation { continuation in
|
|
let geocoder = CLGeocoder()
|
|
let location = CLLocation(latitude: latitude, longitude: longitude)
|
|
geocoder.reverseGeocodeLocation(location) { placemarks, error in
|
|
if let error = error {
|
|
continuation.resume(throwing: error)
|
|
} else if let placemark = placemarks?.first, let name = placemark.name {
|
|
continuation.resume(returning: name)
|
|
} else {
|
|
continuation.resume(throwing: LocationError.reverseGeocode)
|
|
}
|
|
}
|
|
|
|
// TODO: Add cancellation after timeout (20 seconds)
|
|
//geocoder.cancelGeocode()
|
|
}
|
|
}
|
|
|
|
public static func resetLastEvent() {
|
|
self.lastEvent = nil
|
|
}
|
|
|
|
public static func getLastEvent() async -> VehicleEventDto? {
|
|
lastEvent
|
|
}
|
|
}
|