This commit is contained in:
Daniel Arantes Loverde
2026-07-07 15:11:31 -03:00
parent 8b9ebdcb44
commit e51c99973f
293 changed files with 457 additions and 4919 deletions

View File

@@ -0,0 +1,136 @@
import Foundation
#if os(iOS)
import CoreLocation
#endif
@MainActor
final class LocationService: NSObject {
typealias LocationResult = Result<(Double, Double), LocationError>
static let shared = LocationService()
#if os(iOS)
enum LocationError: Error {
case servicesDisabled
case denied
case unavailable
}
#else
enum LocationError: Error {
case denied
case unavailable
}
#endif
#if os(iOS)
private let manager = CLLocationManager()
private var completion: ((LocationResult) -> Void)?
#endif
override init() {
super.init()
#if os(iOS)
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
#endif
}
func requestLocation(_ completion: @escaping (LocationResult) -> Void) {
#if os(iOS)
self.completion = completion
handleAuthorizationStatus(manager.authorizationStatus)
#else
let defaults = UserDefaults.standard
if defaults.bool(forKey: "location_permission_denied") {
completion(.failure(.denied))
return
}
guard let latRaw = defaults.string(forKey: "last_location_lat"),
let lngRaw = defaults.string(forKey: "last_location_lng"),
let lat = Double(latRaw),
let lng = Double(lngRaw) else {
completion(.failure(.unavailable))
return
}
completion(.success((lat, lng)))
#endif
}
func cachedLocation() -> (Double, Double)? {
#if os(iOS)
guard let location = manager.location else {
return nil
}
return (location.coordinate.latitude, location.coordinate.longitude)
#else
let defaults = UserDefaults.standard
guard let latRaw = defaults.string(forKey: "last_location_lat"),
let lngRaw = defaults.string(forKey: "last_location_lng"),
let lat = Double(latRaw),
let lng = Double(lngRaw) else {
return nil
}
return (lat, lng)
#endif
}
func requestLocationAsync(timeoutSeconds: TimeInterval = 8) async -> (Double, Double)? {
await withCheckedContinuation { continuation in
var hasResumed = false
func resumeOnce(_ value: (Double, Double)?) {
guard hasResumed == false else { return }
hasResumed = true
continuation.resume(returning: value)
}
requestLocation { result in
switch result {
case .success(let coordinate):
resumeOnce(coordinate)
case .failure:
resumeOnce(nil)
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + timeoutSeconds) {
resumeOnce(nil)
}
}
}
}
#if os(iOS)
extension LocationService: @preconcurrency CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
handleAuthorizationStatus(manager.authorizationStatus)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
completion?(.success((location.coordinate.latitude, location.coordinate.longitude)))
completion = nil
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
completion?(.failure(.unavailable))
completion = nil
}
private func handleAuthorizationStatus(_ status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .authorizedAlways, .authorizedWhenInUse:
manager.requestLocation()
case .denied, .restricted:
completion?(.failure(.denied))
completion = nil
@unknown default:
completion?(.failure(.unavailable))
completion = nil
}
}
}
#endif