use crate::{EventKitError, Result};
use objc2::rc::Retained;
use objc2_core_location::{CLLocationCoordinate2D, CLLocationManager};
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LocationAuthorizationStatus {
NotDetermined,
Restricted,
Denied,
Authorized,
}
#[derive(Debug, Clone, Copy)]
pub struct Coordinate {
pub latitude: f64,
pub longitude: f64,
}
pub struct LocationManager {
manager: Retained<CLLocationManager>,
}
impl LocationManager {
pub fn new() -> Self {
let manager = unsafe { CLLocationManager::new() };
Self { manager }
}
pub fn authorization_status(&self) -> LocationAuthorizationStatus {
let status = unsafe { self.manager.authorizationStatus() };
match status.0 {
0 => LocationAuthorizationStatus::NotDetermined,
1 => LocationAuthorizationStatus::Restricted,
2 => LocationAuthorizationStatus::Denied,
3 | 4 => LocationAuthorizationStatus::Authorized,
_ => LocationAuthorizationStatus::Denied,
}
}
pub fn request_when_in_use_authorization(&self) {
unsafe {
self.manager.requestWhenInUseAuthorization();
}
}
pub fn cached_location(&self) -> Option<Coordinate> {
let location = unsafe { self.manager.location() }?;
let coord: CLLocationCoordinate2D = unsafe { location.coordinate() };
Some(Coordinate {
latitude: coord.latitude,
longitude: coord.longitude,
})
}
pub fn get_current_location(&self, timeout: Duration) -> Result<Coordinate> {
match self.authorization_status() {
LocationAuthorizationStatus::NotDetermined => {
self.request_when_in_use_authorization();
std::thread::sleep(Duration::from_millis(500));
if self.authorization_status() != LocationAuthorizationStatus::Authorized {
return Err(EventKitError::AuthorizationDenied);
}
}
LocationAuthorizationStatus::Denied => {
return Err(EventKitError::AuthorizationDenied);
}
LocationAuthorizationStatus::Restricted => {
return Err(EventKitError::AuthorizationRestricted);
}
LocationAuthorizationStatus::Authorized => {}
}
unsafe {
self.manager.startUpdatingLocation();
}
let start = std::time::Instant::now();
let result = loop {
if let Some(location) = self.cached_location() {
break Some(location);
}
if start.elapsed() >= timeout {
break None;
}
std::thread::sleep(Duration::from_millis(100));
};
unsafe {
self.manager.stopUpdatingLocation();
}
result.ok_or_else(|| EventKitError::EventKitError("Location request timed out".into()))
}
}
impl Default for LocationManager {
fn default() -> Self {
Self::new()
}
}