// // LocationService.swift // AutoCatCore // // Created by Selim Mustafaev on 31.07.2024. // Copyright © 2024 Selim Mustafaev. All rights reserved. // import CoreLocation import SwiftLocation @MainActor public final class LocationService { let geocoder: GeocoderProtocol let locationManager: SwiftLocationProtocol let settingsService: SettingsServiceProtocol private var eventTask: Task? private(set) public var lastEvent: VehicleEventDto? public init(geocoder: GeocoderProtocol, locationManager: SwiftLocationProtocol, settingsService: SettingsServiceProtocol) { self.geocoder = geocoder self.locationManager = locationManager self.settingsService = settingsService } private func checkPermissions() async throws { switch locationManager.authorizationStatus { case .authorizedWhenInUse, .authorizedAlways: break case .notDetermined: _ = try await locationManager.requestPermission(.always) try await checkPermissions() case .denied: throw CLError(.denied) default: throw LocationError.permission } } private func requestLocation() async throws -> VehicleEventDto { try await checkPermissions() let locationEvent = try await locationManager.requestLocation(accuracy: nil, timeout: 20) guard let coordinate = locationEvent.location?.coordinate else { throw LocationError.generic } return VehicleEventDto(lat: coordinate.latitude, lon: coordinate.longitude, addedBy: settingsService.user.email) } func setLastEvent(_ event: VehicleEventDto) { lastEvent = event } } extension LocationService: LocationServiceProtocol { public func getAddressForLocation(latitude: Double, longitude: Double) async throws -> String { let location = CLLocation(latitude: latitude, longitude: longitude) let placemarks = try await geocoder.reverseGeocodeLocation(location) if let name = placemarks.first?.name { return name } else { throw LocationError.reverseGeocode } } @discardableResult public func requestCurrentLocation() async throws -> VehicleEventDto { if let eventTask { return try await eventTask.value } else { let task = Task { let location = try await requestLocation() eventTask = nil lastEvent = location return location } eventTask = task return try await task.value } } public func getRecentLocation() async throws -> VehicleEventDto { var event: VehicleEventDto if let lastEvent, Date().timeIntervalSince1970 - lastEvent.date < 100 { event = lastEvent } else { event = try await requestCurrentLocation() } event.address = try? await getAddressForLocation(latitude: event.latitude, longitude: event.longitude) return event } public func resetLastEvent() { lastEvent = nil } }