AutoCat/AutoCatCore/Utils/Location.swift

113 lines
3.7 KiB
Swift

import Foundation
import CoreLocation
import SwiftLocation
enum LocationError: LocalizedError {
case generic
case permission
case reverseGeocode
var errorDescription: String? {
switch self {
case .generic: "Location error"
case .permission: "Location permission error"
case .reverseGeocode: "Reverse geocode error"
}
}
}
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 {
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
}
}